JavaScript เป็นภาษาแบบอ้างอิงหรืออ้างอิงผ่านค่าหรือไม่


1404

ประเภทดั้งเดิม (หมายเลข, สตริง, ฯลฯ ) จะถูกส่งผ่านตามค่า แต่วัตถุไม่เป็นที่รู้จักเพราะพวกเขาสามารถเป็นได้ทั้งผ่านโดยค่า (ในกรณีที่เราพิจารณาว่าตัวแปรถือวัตถุในความเป็นจริงการอ้างอิงถึงวัตถุ ) และการอ้างอิงแบบส่งต่อ (เมื่อเราพิจารณาว่าตัวแปรไปยังวัตถุถือวัตถุเอง)

แม้ว่ามันจะไม่สำคัญในตอนท้าย แต่ฉันต้องการที่จะรู้ว่าอะไรคือวิธีที่ถูกต้องในการนำเสนอข้อโต้แย้งผ่านอนุสัญญา มีข้อความที่ตัดตอนมาจากข้อกำหนดของ JavaScript ซึ่งกำหนดสิ่งที่ควรจะเป็นความหมายเกี่ยวกับเรื่องนี้?


2
ฉันคิดว่าคุณบังเอิญพลิกคำจำกัดความของคุณผ่านค่าและส่งผ่านอ้างอิง ... "ผ่านโดยค่า (ในกรณีที่เราพิจารณาว่าตัวแปรถือวัตถุเป็นจริงอ้างอิงถึงวัตถุ) และผ่าน - โดยอ้างอิง (เมื่อเราพิจารณาว่าตัวแปรของวัตถุถือวัตถุเอง) "
Niko Bellic

5
ใช่. โดยไม่คำนึงถึงไวยากรณ์ในการเรียกใช้ฟังก์ชั่นในภาษาการเขียนโปรแกรมใด ๆ การอ้างอิงแบบอ้างอิงถึงหมายความว่าข้อมูลที่เกี่ยวข้องกับตัวแปรที่ส่งผ่านจะไม่ถูกคัดลอกเมื่อผ่านไปยังฟังก์ชั่นดังนั้นการแก้ไขใด ๆ ในโปรแกรมหลังจากการเรียกใช้ฟังก์ชันสิ้นสุดลง Pass-by-value หมายถึงข้อมูลที่เกี่ยวข้องกับตัวแปรจะถูกคัดลอกจริงเมื่อส่งผ่านไปยังฟังก์ชันและการแก้ไขใด ๆ ที่ทำโดยฟังก์ชันดังกล่าวกับตัวแปรดังกล่าวจะหายไปเมื่อตัวแปรออกจากขอบเขตของร่างกายของฟังก์ชันเมื่อฟังก์ชันส่งคืน
John Sonderson

5
คำถามเก่านี้ค่อนข้างเป็นพิษเพราะคำตอบที่ตอบกลับอย่างหนักนั้นไม่ถูกต้อง JavaScript เป็นอย่างเคร่งครัดผ่านโดยค่า
Pointy

6
@DanailNachev คำศัพท์สับสนอย่างน่าเสียใจ สิ่งนี้คือ "การส่งผ่านค่า" และ "การส่งผ่านอ้างอิง" เป็นคำศัพท์ที่มีคุณสมบัติด้านภาษาการเขียนโปรแกรมที่ทันสมัยกว่ามาก คำว่า "คุ้มค่า" และ "อ้างอิง" หมายเฉพาะพารามิเตอร์ตามที่ปรากฏในการแสดงออกของฟังก์ชั่นการโทร JavaScript จะประเมินแต่ละนิพจน์ในรายการพารามิเตอร์การเรียกใช้ฟังก์ชันก่อนเรียกฟังก์ชันดังนั้นพารามิเตอร์จะเป็นค่าเสมอ ส่วนที่สับสนคือการอ้างอิงไปยังวัตถุเป็นค่า JavaScript ทั่วไป แต่นั่นไม่ได้ทำให้มันเป็นภาษา "ส่งต่อโดยอ้างอิง"
Pointy

2
@DanailNachev "ผ่านการอ้างอิง" โดยเฉพาะหมายความว่าถ้าคุณมีvar x=3, y=x; f(x); alert(y === x);แล้วฟังก์ชั่นf()สามารถทำรายงานการแจ้งเตือนและไม่ได้false trueใน JavaScript นั้นเป็นไปไม่ได้ดังนั้นจึงไม่ใช่การอ้างอิงต่อ เป็นเรื่องดีที่มีความเป็นไปได้ที่จะส่งการอ้างอิงไปยังวัตถุที่แก้ไขได้ แต่นั่นไม่ใช่ความหมายของ "การส่งอ้างอิง" อย่างที่ฉันบอกไปมันเป็นความอัปยศที่คำศัพท์สับสนมาก
Pointy

คำตอบ:


1598

มันน่าสนใจใน JavaScript ลองพิจารณาตัวอย่างนี้:

function changeStuff(a, b, c)
{
  a = a * 10;
  b.item = "changed";
  c = {item: "changed"};
}

var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};

changeStuff(num, obj1, obj2);

console.log(num);
console.log(obj1.item);
console.log(obj2.item);

สิ่งนี้สร้างผลลัพธ์:

10
changed
unchanged
  • หากobj1ไม่ใช่การอ้างอิงเลยการเปลี่ยนแปลงobj1.itemจะไม่มีผลกับobj1ภายนอกของฟังก์ชัน
  • หากการโต้แย้งเป็นการอ้างอิงที่เหมาะสมทุกอย่างก็จะเปลี่ยนไป numจะเป็น100และจะอ่านobj2.item"changed"

แต่สถานการณ์คือไอเท็มที่ส่งผ่านถูกแทนที่ด้วยค่า แต่รายการที่ถูกส่งผ่านตามค่านั้นเป็นข้อมูลอ้างอิง เทคนิคนี้เรียกว่าการเรียกร้องโดยใช้งานร่วมกัน

ในแง่การปฏิบัติซึ่งหมายความว่าหากคุณเปลี่ยนพารามิเตอร์เอง (เช่นเดียวกับnumและobj2) ซึ่งจะไม่ส่งผลกระทบต่อรายการที่ป้อนเข้าสู่พารามิเตอร์ แต่ถ้าคุณเปลี่ยนพารามิเตอร์ภายในของมันก็จะทำการสำรองข้อมูล (เช่นเดียวกับobj1)


32
นี่คือสิ่งเดียวกัน (หรืออย่างน้อยความหมาย) เป็น C # วัตถุมีสองประเภท: ค่า (ชนิดดั้งเดิม) และการอ้างอิง
Peter Lee

53
ฉันคิดว่านี่ยังใช้ใน Java: การอ้างอิงโดยค่า
Jpnh

295
เหตุผลที่แท้จริงคือการอ้างอิงใน changeStuff, num, obj1 และ obj2 เมื่อคุณเปลี่ยนitemคุณสมบัติของวัตถุที่อ้างอิงโดย obj1 คุณกำลังเปลี่ยนค่าของคุณสมบัติรายการที่ตั้งค่าไว้ที่ "ไม่เปลี่ยนแปลง" ในตอนแรก เมื่อคุณกำหนด obj2 ค่าของ {item: "change"} คุณกำลังเปลี่ยนการอ้างอิงไปยังวัตถุใหม่ (ซึ่งอยู่นอกขอบเขตทันทีเมื่อฟังก์ชันออก) มันจะชัดเจนมากขึ้นว่าเกิดอะไรขึ้นถ้าคุณตั้งชื่อฟังก์ชั่น params เช่น numf, obj1f และ obj2f จากนั้นคุณจะเห็นว่า params ซ่อนชื่อ var ภายนอกไว้
jinglesthula

13
@BartoNaz ไม่จริง สิ่งที่คุณต้องการคือการส่งการอ้างอิงโดยการอ้างอิงแทนการส่งการอ้างอิงโดยค่า แต่จาวาสคริปต์จะส่งการอ้างอิงตามค่าเสมอเช่นเดียวกับที่มันผ่านทุกอย่างไปตามค่า (สำหรับการเปรียบเทียบ C # มีลักษณะการส่งผ่านอ้างอิงโดยค่าคล้ายกับ JavaScript และ Java แต่ให้คุณระบุรหัสผ่านอ้างอิงโดยอ้างอิงกับrefคำหลัก) โดยปกติแล้วคุณเพียงแค่ให้ฟังก์ชันคืนวัตถุใหม่และทำ การมอบหมายที่จุดที่คุณเรียกใช้ฟังก์ชัน เช่นfoo = GetNewFoo();แทนGetNewFoo(foo);
ทิมกู๊ดแมน

55
แม้ว่าคำตอบนี้จะได้รับความนิยมมากที่สุดมันอาจจะสับสนเล็กน้อยเพราะมันระบุว่า "ถ้ามันเป็นเรื่องของความบริสุทธิ์ตามตัวอักษร" JavaScript เป็นรหัสผ่านที่แท้จริง แต่ค่าที่ส่งผ่านเป็นการอ้างอิง สิ่งนี้ไม่ได้ถูก จำกัด ให้ผ่านพารามิเตอร์เลย คุณสามารถคัดลอกตัวแปรโดยvar obj1 = { item: 'unchanged' }; var obj2 = obj1; obj2.item = 'changed';และจะสังเกตเห็นผลเช่นเดียวกับในตัวอย่างของคุณ ดังนั้นฉันจึงอ้างคำตอบของทิมกู๊ดแมนเป็นการส่วนตัว
chiccodoro

476

มันส่งผ่านค่าเสมอ แต่สำหรับวัตถุค่าของตัวแปรเป็นการอ้างอิง ด้วยเหตุนี้เมื่อคุณส่งวัตถุและเปลี่ยนสมาชิกการเปลี่ยนแปลงเหล่านั้นจะคงอยู่นอกฟังก์ชัน สิ่งนี้ทำให้ดูเหมือนผ่านการอ้างอิง แต่ถ้าคุณเปลี่ยนค่าของตัวแปร object คุณจะเห็นว่าการเปลี่ยนแปลงนั้นไม่คงอยู่พิสูจน์ว่ามันผ่านไปจริง ๆ ตามตัวอักษร

ตัวอย่าง:

function changeObject(x) {
  x = { member: "bar" };
  console.log("in changeObject: " + x.member);
}

function changeMember(x) {
  x.member = "bar";
  console.log("in changeMember: " + x.member);
}

var x = { member: "foo" };

console.log("before changeObject: " + x.member);
changeObject(x);
console.log("after changeObject: " + x.member); /* change did not persist */

console.log("before changeMember: " + x.member);
changeMember(x);
console.log("after changeMember: " + x.member); /* change persists */

เอาท์พุท:

before changeObject: foo
in changeObject: bar
after changeObject: foo

before changeMember: foo
in changeMember: bar
after changeMember: bar

14
@ daylight: ที่จริงแล้วคุณผิด ถ้ามันถูกส่งโดย const อ้างอิงพยายามที่จะทำการเปลี่ยนแปลง Object จะทำให้เกิดข้อผิดพลาดแทนที่จะล้มเหลว ลองกำหนดค่าใหม่ให้กับการอ้างอิง const ใน C ++ และคอมไพเลอร์ปฏิเสธมัน ในแง่ของผู้ใช้นั่นคือความแตกต่างระหว่าง pass by value และ pass by const reference
deworde

5
@daylight: มันไม่ได้อ้างอิงอย่างต่อเนื่อง ในchangeObjectฉันเปลี่ยนxไปมีการอ้างอิงถึงวัตถุใหม่ x = {member:"bar"};เทียบเท่ากับx = new Object(); x.member = "bar"; สิ่งที่ฉันพูดเป็นจริงของ C # โดยวิธี
ทิมกู๊ดแมน

2
@ daylight: สำหรับ C # คุณสามารถดูสิ่งนี้ได้จากนอกฟังก์ชั่นถ้าคุณใช้refคำสำคัญคุณสามารถส่งการอ้างอิงโดยการอ้างอิง (แทนที่จะเป็นค่าเริ่มต้นของการส่งการอ้างอิงโดยค่า) จากนั้นการเปลี่ยนแปลงที่ชี้ไปยังnew Object() จะยังคงอยู่ .
ทิมกู๊ดแมน

11
@adityamenon มันยากที่จะตอบว่า "ทำไม" แต่ฉันจะทราบว่านักออกแบบของ Java และ C # ทำให้ตัวเลือกที่คล้ายกัน; นี่ไม่ใช่แค่ความแปลกของ JavaScript จริงๆแล้วมันมีการส่งผ่านตามค่าอย่างต่อเนื่องสิ่งที่ทำให้คนสับสนคือค่าอาจเป็นข้อมูลอ้างอิงได้ มันไม่แตกต่างจากการส่งตัวชี้ไปรอบ ๆ (ตามค่า) ใน C ++ แล้วยกเลิกการลงทะเบียนเพื่อตั้งค่าสมาชิก ไม่มีใครประหลาดใจที่การเปลี่ยนแปลงนั้นยังคงมีอยู่ แต่เนื่องจากภาษาเหล่านี้ทำให้ตัวชี้ไปและทำให้การพิจารณาเพื่อคุณเงียบลงผู้คนจึงสับสน
ทิมกู๊ดแมน

41
กล่าวอีกนัยหนึ่งสิ่งที่ทำให้สับสนที่นี่ไม่ใช่การอ้างอิงถึงค่า / pass-by-reference ทุกอย่างเป็นค่าผ่านจุดหยุดเต็มรูปแบบ สิ่งที่สับสนคือคุณไม่สามารถส่งผ่านวัตถุได้และไม่สามารถเก็บวัตถุไว้ในตัวแปรได้ เวลาที่คุณทุกคนคิดว่าคุณกำลังทำที่คุณจริงผ่านหรือการจัดเก็บการอ้างอิงไปยังวัตถุที่ แต่เมื่อคุณไปถึงสมาชิกมันมีการพูดคุยกันเงียบ ๆ ที่เกิดขึ้นนั่นทำให้นวนิยายที่ตัวแปรของคุณเก็บไว้เป็นวัตถุจริง
ทิมกู๊ดแมน

150

ตัวแปรไม่ "ถือ" วัตถุ; มันมีการอ้างอิง คุณสามารถกำหนดการอ้างอิงนั้นให้กับตัวแปรอื่นและตอนนี้ทั้งคู่อ้างอิงวัตถุเดียวกัน มันส่งผ่านค่าเสมอ (แม้ว่าค่านั้นจะเป็นการอ้างอิง ... )

ไม่มีทางที่จะแก้ไขค่าที่เก็บไว้โดยตัวแปรที่ส่งผ่านเป็นพารามิเตอร์ซึ่งจะเป็นไปได้ถ้า JavaScript รองรับการส่งผ่านโดยการอ้างอิง


2
นี่ทำให้ฉันสับสนเล็กน้อย ไม่ได้ส่งการอ้างอิงแบบอ้างอิงถึงการอ้างอิงหรือไม่

8
ผู้เขียนหมายถึงว่าโดยการส่งการอ้างอิงคุณกำลังผ่านค่าการอ้างอิง (อีกวิธีหนึ่งในการคิดว่าการส่งผ่านค่าของที่อยู่หน่วยความจำ) นั่นคือเหตุผลที่ถ้าคุณประกาศวัตถุใหม่ต้นฉบับจะไม่เปลี่ยนแปลงเพราะคุณกำลังสร้างวัตถุใหม่ที่ตำแหน่งหน่วยความจำที่แตกต่างกัน หากคุณเปลี่ยนคุณสมบัติวัตถุต้นฉบับจะเปลี่ยนเนื่องจากคุณเปลี่ยนที่ตำแหน่งหน่วยความจำดั้งเดิม (ที่ไม่ได้ถูกกำหนดใหม่)
Huy-Anh Hoang

113

สองเซ็นต์ของฉัน ... นี่คือวิธีที่ฉันเข้าใจ (อย่าลังเลที่จะแก้ไขฉันถ้าฉันผิด)

ได้เวลาโยนทุกสิ่งที่คุณรู้เกี่ยวกับการส่งผ่านตามค่า / การอ้างอิง

เพราะในจาวาสคริปต์มันไม่สำคัญว่าจะถูกส่งผ่านตามค่าหรืออ้างอิงหรืออะไรก็ตาม สิ่งที่สำคัญคือการกลายพันธุ์และการกำหนดพารามิเตอร์ที่ส่งผ่านไปยังฟังก์ชัน

ตกลงให้ฉันทำอย่างดีที่สุดเพื่ออธิบายสิ่งที่ฉันหมายถึง สมมติว่าคุณมีวัตถุบางอย่าง

var object1 = {};
var object2 = {};

สิ่งที่เราทำคือ "การมอบหมาย" ... เราได้กำหนดวัตถุว่างเปล่า 2 อันแยกกันให้กับตัวแปร "object1" และ "object2"

ทีนี้สมมติว่าเราชอบ object1 ดีกว่า ... ดังนั้นเรา "กำหนด" ตัวแปรใหม่

var favoriteObject = object1;

ต่อไปไม่ว่าด้วยเหตุผลใดเราตัดสินใจว่าเราชอบวัตถุ 2 ดีกว่า ดังนั้นเราเพียงแค่ทำการมอบหมายใหม่เล็กน้อย

favoriteObject = object2;

ไม่มีอะไรเกิดขึ้นกับ object1 หรือ object2 เราไม่ได้เปลี่ยนแปลงข้อมูลใด ๆ เลย สิ่งที่เราทำก็คือกำหนดสิ่งที่วัตถุที่เราโปรดปรานอีกครั้ง สิ่งสำคัญคือต้องรู้ว่า object2 และ FavoriteObject ได้รับการกำหนดให้กับวัตถุเดียวกัน เราสามารถเปลี่ยนวัตถุนั้นผ่านตัวแปรเหล่านั้น

object2.name = 'Fred';
console.log(favoriteObject.name) // Logs Fred
favoriteObject.name = 'Joe';
console.log(object2.name); // Logs Joe

ตกลงตอนนี้เรามาดูตัวอย่างพื้นฐานเช่นสตริง

var string1 = 'Hello world';
var string2 = 'Goodbye world';

อีกครั้งเราเลือกรายการโปรด

var favoriteString = string1;

ทั้งตัวแปร FavoriteString และ string1 ของเราถูกกำหนดให้กับ 'Hello world' ทีนี้ถ้าหากเราต้องการเปลี่ยนแถบรายการโปรดของเรา ??? อะไรจะเกิดขึ้น???

favoriteString = 'Hello everyone';
console.log(favoriteString); // Logs 'Hello everyone'
console.log(string1); // Logs 'Hello world'

อ๊ะ .... เกิดอะไรขึ้น เราไม่สามารถเปลี่ยน string1 ได้โดยเปลี่ยนสายรายการโปรด ... ทำไม ?? เพราะเราไม่ได้เปลี่ยนสตริงของเราวัตถุ สิ่งที่เราทำคือ "RE ASSIGN" ตัวแปร FavoriteString เป็นสตริงใหม่ สิ่งนี้ตัดการเชื่อมต่อจาก string1 เป็นหลัก ในตัวอย่างก่อนหน้านี้เมื่อเราเปลี่ยนชื่อวัตถุเราไม่ได้มอบหมายอะไรเลย (ไม่ใช่สำหรับตัวแปรเอง ... เราทำ แต่กำหนดคุณสมบัติชื่อให้กับสตริงใหม่) แต่เราเพียงแค่กลายพันธุ์วัตถุซึ่งทำให้การเชื่อมต่อระหว่างตัวแปร 2 กับวัตถุต้นแบบ (แม้ว่าเราต้องการแก้ไขหรือกลายพันธุ์วัตถุสตริงเองเราไม่สามารถทำได้เพราะสตริงนั้นไม่เปลี่ยนรูปได้จริงใน JavaScript)

ทีนี้ไปที่ฟังก์ชั่นและพารามิเตอร์ที่ส่งผ่าน .... เมื่อคุณเรียกใช้ฟังก์ชั่นและส่งผ่านพารามิเตอร์สิ่งที่คุณกำลังทำอยู่ก็คือ "การมอบหมาย" ให้กับตัวแปรใหม่และมันทำงานเหมือนกับที่คุณได้รับมอบหมาย เครื่องหมายเท่ากับ (=)

นำตัวอย่างเหล่านี้

var myString = 'hello';

// Assign to a new variable (just like when you pass to a function)
var param1 = myString;
param1 = 'world'; // Re assignment

console.log(myString); // Logs 'hello'
console.log(param1);   // Logs 'world'

ตอนนี้สิ่งเดียวกัน แต่มีฟังก์ชั่น

function myFunc(param1) {
    param1 = 'world';

    console.log(param1);   // Logs 'world'
}

var myString = 'hello';
// Calls myFunc and assigns param1 to myString just like param1 = myString
myFunc(myString);

console.log(myString); // logs 'hello'

ตกลงตอนนี้เราจะยกตัวอย่างการใช้วัตถุแทน ... ก่อนโดยไม่มีฟังก์ชั่น

var myObject = {
    firstName: 'Joe',
    lastName: 'Smith'
};

// Assign to a new variable (just like when you pass to a function)
var otherObj = myObject;

// Let's mutate our object
otherObj.firstName = 'Sue'; // I guess Joe decided to be a girl

console.log(myObject.firstName); // Logs 'Sue'
console.log(otherObj.firstName); // Logs 'Sue'

// Now, let's reassign the variable
otherObj = {
    firstName: 'Jack',
    lastName: 'Frost'
};

// Now, otherObj and myObject are assigned to 2 very different objects
// And mutating one object has no influence on the other
console.log(myObject.firstName); // Logs 'Sue'
console.log(otherObj.firstName); // Logs 'Jack';

ตอนนี้สิ่งเดียวกัน แต่มีฟังก์ชั่นการโทร

function myFunc(otherObj) {

    // Let's mutate our object
    otherObj.firstName = 'Sue';
    console.log(otherObj.firstName); // Logs 'Sue'

    // Now let's re-assign
    otherObj = {
        firstName: 'Jack',
        lastName: 'Frost'
    };
    console.log(otherObj.firstName); // Logs 'Jack'

    // Again, otherObj and myObject are assigned to 2 very different objects
    // And mutating one object doesn't magically mutate the other
}

var myObject = {
    firstName: 'Joe',
    lastName: 'Smith'
};

// Calls myFunc and assigns otherObj to myObject just like otherObj = myObject
myFunc(myObject);

console.log(myObject.firstName); // Logs 'Sue', just like before

ตกลงถ้าคุณอ่านโพสต์ทั้งหมดนี้บางทีตอนนี้คุณมีความเข้าใจที่ดีขึ้นเกี่ยวกับการทำงานของการโทรใน JavaScript มันไม่สำคัญว่าจะมีบางสิ่งบางอย่างถูกส่งผ่านโดยการอ้างอิงหรือตามค่า ... สิ่งสำคัญคือการมอบหมายกับการกลายพันธุ์

ทุกครั้งที่คุณส่งตัวแปรไปยังฟังก์ชั่นคุณจะ "กำหนด" ให้กับชื่อของตัวแปรพารามิเตอร์ใด ๆ เช่นถ้าคุณใช้เครื่องหมายเท่ากับ (=)

โปรดจำไว้เสมอว่าเครื่องหมายเท่ากับ (=) หมายถึงการมอบหมาย โปรดจำไว้เสมอว่าการส่งพารามิเตอร์ไปยังฟังก์ชันใน JavaScriptก็หมายถึงการกำหนดเช่นกัน พวกเขาเหมือนกันและตัวแปร 2 ตัวเชื่อมต่อกันด้วยวิธีเดียวกัน (ซึ่งก็คือพวกเขาไม่ได้เว้นแต่ว่าคุณจะได้รับมอบหมายให้เป็นวัตถุเดียวกัน)

ครั้งเดียวที่ "การแก้ไขตัวแปร" มีผลต่อตัวแปรที่แตกต่างกันคือเมื่อวัตถุต้นแบบถูกทำให้กลายพันธุ์ (ซึ่งในกรณีนี้คุณยังไม่ได้แก้ไขตัวแปร แต่เป็นวัตถุเอง

ไม่มีจุดใดที่ทำให้ความแตกต่างระหว่างวัตถุกับวัตถุพื้นฐานเพราะมันทำงานในลักษณะเดียวกันกับที่คุณไม่มีฟังก์ชั่นและใช้เครื่องหมายเท่ากับเพื่อกำหนดให้กับตัวแปรใหม่

gotcha เพียงอย่างเดียวคือเมื่อชื่อของตัวแปรที่คุณส่งเข้าไปในฟังก์ชันนั้นเหมือนกับชื่อของพารามิเตอร์ฟังก์ชัน เมื่อสิ่งนี้เกิดขึ้นคุณจะต้องปฏิบัติต่อพารามิเตอร์ภายในฟังก์ชั่นราวกับว่ามันเป็นตัวแปรใหม่ทั้งหมดของฟังก์ชั่นส่วนตัว (เพราะมันเป็น)

function myFunc(myString) {
    // myString is private and does not affect the outer variable
    myString = 'hello';
}

var myString = 'test';
myString = myString; // Does nothing, myString is still 'test';

myFunc(myString);
console.log(myString); // Logs 'test'

2
สำหรับโปรแกรมเมอร์ C คนใด ๆ ให้นึกถึง char * foo(char *a){a="hello";} ไม่ทำอะไรเลย แต่ถ้าคุณทำfoo(char *a){a[0]='h';a[1]='i';a[2]=0;}มันเปลี่ยนไปข้างนอกเพราะaเป็นที่ตั้งหน่วยความจำที่ส่งผ่านโดยค่าที่อ้างอิงสตริง (อาร์เรย์ถ่าน) อนุญาตให้ส่งผ่าน structs (คล้ายกับวัตถุ js) ตามค่าใน C แต่ไม่แนะนำ JavaScript บังคับใช้แนวปฏิบัติที่ดีที่สุดเหล่านี้และซ่อน cruft ที่ไม่จำเป็นและมักจะไม่ต้องการ ... และแน่นอนว่าทำให้อ่านง่ายขึ้น
technosaurus

2
สิ่งนี้ถูกต้อง - คำว่าpass-by-valueและpass-by-referenceมีความหมายในการออกแบบภาษาโปรแกรมและความหมายเหล่านั้นไม่มีส่วนเกี่ยวข้องกับการกลายพันธุ์ของวัตถุ มันคือทั้งหมดที่เกี่ยวกับการทำงานของพารามิเตอร์ฟังก์ชัน
แหลม

2
ตอนนี้ฉันเข้าใจแล้วว่า obj1 = obj2 หมายความว่าทั้ง obj1 และ obj2 นั้นชี้ไปที่ตำแหน่งอ้างอิงเดียวกันและถ้าฉันแก้ไข internals ของ obj2 การอ้างอิง obj1 จะทำให้เกิด internals เดียวกัน ฉันจะคัดลอกวัตถุอย่างไรเมื่อฉันทำsource = { "id":"1"}; copy = source /*this is wrong*/; copy.id="2"แหล่งนั้นยังคง {"id": "1"}?
Machtyn

1
ฉันโพสต์คำตอบอื่นด้วยคำจำกัดความดั้งเดิมเพื่อหวังลดความสับสน คำจำกัดความดั้งเดิมของ "pass-by-value" และ "pass-by-reference" ถูกกำหนดกลับมาในวันที่ตัวชี้หน่วยความจำก่อนการลงทะเบียนอัตโนมัติ เป็นที่เข้าใจกันอย่างดีว่าค่าของตัวแปรวัตถุคือตำแหน่งตัวชี้หน่วยความจำไม่ใช่วัตถุ แม้ว่าการอภิปรายเกี่ยวกับการมอบหมายการกลายพันธุ์และการกลายพันธุ์ของคุณอาจมีประโยชน์ แต่ก็ไม่จำเป็นที่จะต้องทิ้งข้อกำหนดดั้งเดิมหรือคำจำกัดความ การกลายพันธุ์การมอบหมาย Pass-by-value, Pass-by-Reference และอื่น ๆ จะต้องไม่ขัดแย้งกัน
C Perkins

"Number" เป็น "ไม่เปลี่ยนรูป" เกินไปหรือไม่
ebram khalil

72

พิจารณาสิ่งต่อไปนี้:

  1. ตัวแปรคือพอยน์เตอร์ของค่าในหน่วยความจำ
  2. การกำหนดตัวแปรใหม่เพียงชี้ตัวชี้ที่ค่าใหม่
  3. การกำหนดตัวแปรใหม่จะไม่ส่งผลกระทบต่อตัวแปรอื่น ๆ ที่ชี้ไปที่วัตถุเดียวกัน

ดังนั้นอย่าลืม"pass by reference / value"อย่าไปวางบน "pass by reference / value" เพราะ:

  1. ข้อกำหนดนี้ใช้เพื่ออธิบายพฤติกรรมของภาษาเท่านั้นไม่จำเป็นต้องเป็นการใช้งานจริง อันเป็นผลมาจากสิ่งที่เป็นนามธรรมนี้รายละเอียดที่สำคัญที่จำเป็นสำหรับคำอธิบายที่เหมาะสมจะหายไปซึ่งนำไปสู่สถานการณ์ปัจจุบันอย่างหลีกเลี่ยงไม่ได้ซึ่งคำเดียวไม่เพียงพอที่จะอธิบายพฤติกรรมจริงและข้อมูลเสริมที่เพียงพอ
  2. แนวคิดเหล่านี้ไม่ได้ถูกกำหนดไว้ในตอนแรกด้วยเจตนาที่จะอธิบายจาวาสคริปต์โดยเฉพาะดังนั้นฉันจึงไม่รู้สึกว่าถูกบังคับให้ใช้เมื่อพวกเขาเพิ่มความสับสนเท่านั้น

ในการตอบคำถามของคุณ: ผ่านตัวชี้


// code
var obj = {
    name: 'Fred',
    num: 1
};

// illustration
               'Fred'
              /
             /
(obj) ---- {}
             \
              \
               1


// code
obj.name = 'George';


// illustration
                 'Fred'


(obj) ---- {} ----- 'George'
             \
              \
               1


// code
obj = {};

// illustration
                 'Fred'


(obj)      {} ----- 'George'
  |          \
  |           \
 { }            1


// code
var obj = {
    text: 'Hello world!'
};

/* function parameters get their own pointer to 
 * the arguments that are passed in, just like any other variable */
someFunc(obj);


// illustration
(caller scope)        (someFunc scope)
           \             /
            \           /
             \         /
              \       /
               \     /
                 { }
                  |
                  |
                  |
            'Hello world'

ความเห็นสุดท้าย:

  • มันเป็นเรื่องดึงดูดที่จะคิดว่าสิ่งดั้งเดิมถูกบังคับใช้โดยกฎพิเศษในขณะที่วัตถุไม่ได้เป็น แต่ดั้งเดิมนั้นเป็นเพียงจุดสิ้นสุดของห่วงโซ่ตัวชี้
  • เป็นตัวอย่างขั้นสุดท้ายพิจารณาว่าทำไมความพยายามทั่วไปในการล้างข้อมูลอาร์เรย์ไม่ทำงานอย่างที่คาดไว้


var a = [1,2];
var b = a;

a = [];
console.log(b); // [1,2]
// doesn't work because `b` is still pointing at the original array

คำถามติดตามเครดิตพิเศษ;) การรวบรวมขยะทำงานอย่างไร หากฉันวนรอบตัวแปรผ่านหนึ่งล้าน{'George', 1}ค่า แต่ใช้เพียงหนึ่งค่าต่อครั้งดังนั้นค่าอื่น ๆ จะถูกจัดการอย่างไร และจะเกิดอะไรขึ้นเมื่อฉันกำหนดตัวแปรให้กับค่าของตัวแปรอื่น ฉันจะชี้ไปที่ตัวชี้หรือชี้ไปที่พอยท์ของตัวถูกดำเนินการที่เหมาะสมหรือไม่ มีvar myExistingVar = {"blah", 42}; var obj = myExistingVar;ผลในการobjชี้ไปที่{"blah", 42}หรือเพื่อmyExistingVar?
Michael Hoffmann

@MichaelHoffmann เหล่านี้สมควรได้รับคำถาม SO ของตัวเองและอาจตอบคำถามได้ดีกว่าที่ฉันสามารถจัดการได้ ที่ถูกกล่าวว่า1)ฉันวิ่งโปรไฟล์หน่วยความจำในเครื่องมือ dev ของเบราว์เซอร์สำหรับฟังก์ชั่นการวนซ้ำเช่นคนที่คุณอธิบายและเห็นการใช้หน่วยความจำ spikes ตลอดกระบวนการลูป ดูเหมือนว่าสิ่งนี้จะบ่งบอกว่าวัตถุที่เหมือนกันใหม่ถูกสร้างขึ้นอย่างแน่นอนในการวนซ้ำแต่ละรอบ เมื่อเดือยแหลมตกลงมาตัวเก็บขยะก็ทำความสะอาดกลุ่มของวัตถุที่ไม่ได้ใช้เหล่านี้
geg

1
@MichaelHoffmann 2)เกี่ยวกับสิ่งที่ชอบvar a = bjavascript ไม่มีกลไกในการใช้พอยน์เตอร์ดังนั้นตัวแปรไม่สามารถชี้ไปที่ตัวชี้ได้ (อย่างที่คุณสามารถทำได้ใน C) แม้ว่าเอ็นจิ้นจาวาสคริปต์พื้นฐานจะใช้มันอย่างไม่ต้องสงสัย ดังนั้น ... var a = bจะชี้a" ชี้ไปที่ผู้รับชี้แนะของตัวถูก"
geg

ฉันถามคำถาม # 1 ที่นี่ (โดยเฉพาะเกี่ยวกับ Chrome เนื่องจากการใช้งานอาจแตกต่างกันในทุกเบราว์เซอร์) stackoverflow.com/q/42778439/539997และฉันยังคงพยายามคิดว่าจะถามคำถาม # 2 อย่างไร ความช่วยเหลือใด ๆ ที่ชื่นชม
Michael Hoffmann

1
ไม่จำเป็นต้องลืมเกี่ยวกับ "ผ่านการอ้างอิง / ค่า" ! คำเหล่านี้มีความหมายทางประวัติศาสตร์ที่อธิบายสิ่งที่คุณพยายามจะอธิบาย หากเราทิ้งข้อกำหนดและคำจำกัดความทางประวัติศาสตร์และขี้เกียจเกินกว่าที่จะเรียนรู้สิ่งที่พวกเขาหมายถึงเดิมเราจะสูญเสียความสามารถในการสื่อสารอย่างมีประสิทธิภาพระหว่างรุ่น ไม่มีวิธีที่ดีที่จะพูดคุยถึงความแตกต่างระหว่างภาษาและระบบที่ต่างกัน แต่โปรแกรมเมอร์คนใหม่จำเป็นต้องเรียนรู้และเข้าใจเงื่อนไขดั้งเดิมและสาเหตุที่มาของมัน มิฉะนั้นเราจะสูญเสียความรู้และความเข้าใจโดยรวม
C Perkins

24

วัตถุนอกฟังก์ชั่นจะถูกส่งผ่านไปยังฟังก์ชั่นโดยให้การอ้างอิงถึงวัตถุภายนอก

เมื่อคุณใช้การอ้างอิงนั้นเพื่อจัดการกับวัตถุวัตถุภายนอกจึงได้รับผลกระทบ อย่างไรก็ตามถ้าภายในฟังก์ชั่นที่คุณตัดสินใจชี้การอ้างอิงไปยังสิ่งอื่นคุณจะไม่ส่งผลกระทบต่อวัตถุภายนอกเลยเนื่องจากสิ่งที่คุณทำนั้นเป็นการอ้างอิงโดยตรงไปยังสิ่งอื่น


20

ลองคิดแบบนี้: มันผ่านคุณค่ามาเสมอ อย่างไรก็ตามค่าของวัตถุนั้นไม่ใช่วัตถุ แต่เป็นการอ้างอิงไปยังวัตถุนั้น

นี่คือตัวอย่างการส่งตัวเลข (ชนิดดั้งเดิม)

function changePrimitive(val) {
    // At this point there are two '10's in memory.
    // Changing one won't affect the other
    val = val * 10;
}
var x = 10;
changePrimitive(x);
// x === 10

การทำสิ่งนี้ซ้ำกับวัตถุจะให้ผลลัพธ์ที่ต่างกัน:

function changeObject(obj) {
    // At this point there are two references (x and obj) in memory,
    // but these both point to the same object.
    // changing the object will change the underlying object that
    // x and obj both hold a reference to.
    obj.val = obj.val * 10;
}
var x = { val: 10 };
changeObject(x);
// x === { val: 100 }

อีกหนึ่งตัวอย่าง:

function changeObject(obj) {
    // Again there are two references (x and obj) in memory,
    // these both point to the same object.
    // now we create a completely new object and assign it.
    // obj's reference now points to the new object.
    // x's reference doesn't change.
    obj = { val: 100 };
}
var x = { val: 10 };
changeObject(x);
// x === { val: 10}

19

คำอธิบายรายละเอียดมากเกี่ยวกับการคัดลอกผ่านและเปรียบเทียบโดยค่าและโดยการอ้างอิงอยู่ในบทนี้ของ"JavaScript: คู่มือการแตกหัก"หนังสือเล่มนี้

ก่อนที่เราจะออกจากหัวข้อของการจัดการออบเจ็กต์และอาร์เรย์โดยการอ้างอิงเราจำเป็นต้องเคลียร์ประเด็นของระบบการตั้งชื่อ

วลี "ส่งต่อโดยอ้างอิง" สามารถมีความหมายหลายอย่าง สำหรับผู้อ่านบางคนวลีนี้อ้างถึงเทคนิคการเรียกใช้ฟังก์ชันที่อนุญาตให้ฟังก์ชันกำหนดค่าใหม่ให้กับอาร์กิวเมนต์และเพื่อให้ค่าที่แก้ไขเหล่านั้นสามารถมองเห็นได้นอกฟังก์ชัน นี่ไม่ใช่วิธีการใช้คำในหนังสือเล่มนี้

ที่นี่เราหมายถึงเพียงแค่การอ้างอิงไปยังวัตถุหรืออาร์เรย์ - ไม่ใช่วัตถุเอง - ถูกส่งผ่านไปยังฟังก์ชั่น ฟังก์ชั่นสามารถใช้การอ้างอิงเพื่อปรับเปลี่ยนคุณสมบัติของวัตถุหรือองค์ประกอบของอาร์เรย์ แต่ถ้าฟังก์ชันเขียนทับการอ้างอิงด้วยการอ้างอิงไปยังวัตถุหรืออาร์เรย์ใหม่การแก้ไขนั้นจะไม่สามารถมองเห็นได้นอกฟังก์ชั่น

ผู้อ่านที่คุ้นเคยกับความหมายอื่นของคำนี้อาจต้องการบอกว่าวัตถุและอาร์เรย์ถูกส่งผ่านตามค่า แต่ค่าที่ส่งผ่านนั้นเป็นการอ้างอิงจริง ๆ แทนที่จะเป็นวัตถุเอง


ว้าวนี่มันสับสนอย่างไม่น่าเชื่อ ใครในใจที่ถูกต้องของพวกเขาจะกำหนดคำที่มีการจัดตั้งอย่างดีเพื่อหมายถึงสิ่งที่ตรงกันข้ามและใช้มันอย่างนั้น? ไม่น่าแปลกใจที่คำตอบมากมายที่นี่ในคำถามนี้สับสนมาก
Jörg W Mittag

16

JavaScript มักจะผ่านโดยค่า ; ทุกอย่างเป็นประเภทค่า

วัตถุคือค่าและฟังก์ชั่นสมาชิกของวัตถุเป็นค่าตัวเอง (โปรดจำไว้ว่าฟังก์ชั่นเป็นวัตถุชั้นหนึ่งใน JavaScript) นอกจากนี้เกี่ยวกับแนวคิดที่ว่าทุกอย่างใน JavaScript เป็นวัตถุ ; นี่มันผิด สตริง, สัญลักษณ์ตัวเลข booleans, nulls และ undefineds เป็นพื้นฐาน

ในบางครั้งพวกเขาสามารถใช้ประโยชน์จากฟังก์ชั่นสมาชิกและคุณสมบัติบางอย่างที่สืบทอดมาจากต้นแบบพื้นฐานของพวกเขา แต่นี่ก็เพื่อความสะดวกเท่านั้น ไม่ได้หมายความว่าพวกเขาเป็นวัตถุเอง ลองต่อไปนี้สำหรับการอ้างอิง:

x = "test";
alert(x.foo);
x.foo = 12;
alert(x.foo);

ในการเตือนทั้งสองคุณจะพบค่าที่ไม่ได้กำหนด


12
-1 มันไม่ได้ผ่านค่าเสมอ จาก MDC: "ถ้าคุณผ่านวัตถุ (เช่นค่าที่ไม่ใช่แบบดั้งเดิมเช่น Array หรือวัตถุที่ผู้ใช้กำหนดเอง) เป็นพารามิเตอร์การอ้างอิงไปยังวัตถุนั้นจะถูกส่งผ่านไปยังฟังก์ชัน"
นิค

37
@Nick: มันมักจะผ่านค่า ระยะเวลา การอ้างอิงไปยังวัตถุถูกส่งผ่านโดยค่าไปยังฟังก์ชัน ไม่ผ่านการอ้างอิง "ส่งผ่านการอ้างอิง" เกือบจะคิดว่าผ่านตัวแปรเองมากกว่าค่า; การเปลี่ยนแปลงใด ๆที่ฟังก์ชันทำกับอาร์กิวเมนต์ (รวมถึงการแทนที่ด้วยวัตถุที่แตกต่างกันโดยสิ้นเชิง!) จะปรากฏในตัวเรียก บิตสุดท้ายนั้นเป็นไปไม่ได้ใน JS เนื่องจากJS ไม่ผ่านการอ้างอิง - มันผ่านการอ้างอิงตามค่า ความแตกต่างนั้นบอบบาง แต่ค่อนข้างสำคัญที่จะเข้าใจข้อ จำกัด ของมัน
cHao

1
สำหรับตัวเรียงลำดับในอนาคต ... เกี่ยวกับการอ้างอิงของคุณ: x = "teste"; x.foo = 12;ฯลฯ เนื่องจากคุณสมบัติไม่คงอยู่ไม่ได้หมายความว่ามันไม่ใช่วัตถุ MDN พูดว่า: ใน JavaScript เกือบทุกอย่างเป็นวัตถุ ดึกดำบรรพ์ทุกประเภทยกเว้นโมฆะและไม่ได้กำหนดจะถือว่าเป็นวัตถุ พวกเขาสามารถกำหนดคุณสมบัติ (คุณสมบัติที่กำหนดของบางประเภทไม่ถาวร) และพวกเขามีลักษณะทั้งหมดของวัตถุ link
slacktracer

9
MDN เป็นวิกิที่แก้ไขโดยผู้ใช้และมีความผิดพลาด ข้อมูลอ้างอิงเชิงบรรทัดคือ ECMA-262 ดู S. 8 "ประเภทข้อมูลจำเพาะอ้างอิง" ซึ่งอธิบายวิธีแก้ไขการอ้างอิงและ 8.12.5 "[[ใส่]]" ซึ่งใช้เพื่ออธิบายการมอบหมายการแสดงออกถึงการอ้างอิงและการเชื่อมโยงวัตถุ 9.9 ToObject สำหรับค่าดั้งเดิม Michael ได้อธิบายสิ่งที่ ToObject ทำแล้วตามที่ระบุไว้ แต่เห็นด้วย 4.3.2 ค่าดั้งเดิม
การ์เร็ต

1
@WonderLand: ไม่เขาไม่ใช่ คนที่ไม่เคยผ่านการอ้างอิงอาจไม่เคยเข้าใจความแตกต่างระหว่างการส่งต่อโดยอ้างอิงและส่งผ่านการอ้างอิงตามค่า แต่พวกเขาอยู่ที่นั่นและพวกเขามีความสำคัญ ฉันไม่สนใจที่จะเข้าใจผิดคนแค่ทำให้ฟังดูง่ายขึ้น
cHao

12

ใน JavaScript ชนิดของค่าแต่เพียงผู้เดียวควบคุมว่าคุ้มค่าที่จะได้รับมอบหมายจากมูลค่าการคัดลอกหรือโดยการอ้างอิงการคัดลอก

ค่าดั้งเดิมที่ได้รับมอบหมายเสมอ / ผ่านค่าสำเนา :

  • null
  • undefined
  • เชือก
  • จำนวน
  • บูล
  • สัญลักษณ์ ES6

ค่า Compound จะถูกกำหนด / ส่งผ่านโดย Reference-copy เสมอ

  • วัตถุ
  • อาร์เรย์
  • ฟังก์ชัน

ตัวอย่างเช่น

var a = 2;
var b = a; // `b` is always a copy of the value in `a`
b++;
a; // 2
b; // 3

var c = [1,2,3];
var d = c; // `d` is a reference to the shared `[1,2,3]` value
d.push( 4 );
c; // [1,2,3,4]
d; // [1,2,3,4]

ในตัวอย่างด้านบนเนื่องจาก2เป็นสเกลาร์ดั้งเดิมaเก็บสำเนาแรกเริ่มของค่านั้นและbได้รับสำเนาอีกชุดของค่า เมื่อมีการเปลี่ยนแปลงที่คุณอยู่ในวิธีการเปลี่ยนค่าในไม่มีba

แต่ทั้งคู่cและdเป็นการอ้างอิงแยกกันไปยังค่าแชร์เดียวกัน[1,2,3]ซึ่งเป็นค่าผสม มันเป็นสิ่งสำคัญที่จะทราบว่าค่าcมิได้dมากขึ้น "เจ้าของ" ความ[1,2,3]คุ้มค่า - ทั้งเป็นเพียงการอ้างอิงเพียร์เท่ากับค่า ดังนั้นเมื่อใช้การอ้างอิงอย่างใดอย่างหนึ่งในการปรับเปลี่ยน ( .push(4)) ที่ใช้ร่วมกันที่เกิดขึ้นจริงค่าตัวเองก็มีผลกระทบต่อเพียงหนึ่งค่าใช้ร่วมกันและทั้งสองจะอ้างอิงอ้างอิงค่าแก้ไขใหม่array[1,2,3,4]

var a = [1,2,3];
var b = a;
a; // [1,2,3]
b; // [1,2,3]

// later
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]

เมื่อเราทำการมอบหมายb = [4,5,6]เราไม่ได้ทำอะไรที่จะส่งผลกระทบต่อที่aยังคงอ้างอิง ( [1,2,3]) ในการทำเช่นนั้นbจะต้องเป็นตัวชี้ไปaแทนที่จะอ้างอิงถึงarray- แต่ไม่มีความสามารถดังกล่าวใน JS!

function foo(x) {
    x.push( 4 );
    x; // [1,2,3,4]

    // later
    x = [4,5,6];
    x.push( 7 );
    x; // [4,5,6,7]
}

var a = [1,2,3];

foo( a );

a; // [1,2,3,4]  not  [4,5,6,7]

เมื่อเราผ่านในการโต้แย้งaก็กำหนดสำเนาของการอ้างอิงถึงa และเป็นการอ้างอิงแยกต่างหากที่ชี้ไปที่ค่าเดียวกัน ตอนนี้ภายในฟังก์ชั่นเราสามารถใช้การอ้างอิงนั้นเพื่อเปลี่ยนแปลงค่าตัวมันเอง ( ) แต่เมื่อเราทำให้ได้รับมอบหมายนี้เป็นในทางที่มีผลต่อการที่มีการอ้างอิงที่เริ่มต้นไม่ชี้ - ยังคงชี้ที่ (แก้ไขตอนนี้) มูลค่าxxa[1,2,3]push(4)x = [4,5,6]a[1,2,3,4]

ในการส่งผ่านค่าผสม (เช่นarray) ตามค่าสำเนาอย่างมีประสิทธิภาพคุณจำเป็นต้องทำสำเนาด้วยตนเองเพื่อให้การอ้างอิงที่ส่งผ่านยังไม่ชี้ไปที่ต้นฉบับ ตัวอย่างเช่น:

foo( a.slice() );

Compound value (object, array, ฯลฯ ) ที่สามารถส่งผ่านโดยการอ้างอิง - คัดลอก

function foo(wrapper) {
    wrapper.a = 42;
}

var obj = {
    a: 2
};

foo( obj );

obj.a; // 42

ที่นี่ทำหน้าที่เป็นเสื้อคลุมสำหรับเกลาคุณสมบัติดั้งเดิมobj aเมื่อส่งผ่านไปfoo(..)สำเนาของการobjอ้างอิงจะถูกส่งผ่านและตั้งค่าเป็นwrapperพารามิเตอร์ ตอนนี้เราสามารถใช้การwrapperอ้างอิงเพื่อเข้าถึงวัตถุที่ใช้ร่วมกันและอัปเดตคุณสมบัติของมัน หลังจากเสร็จสิ้นการทำงานที่จะเห็นค่าปรับปรุงobj.a42

แหล่ง


คุณระบุสถานะ "ค่า Compound จะถูกกำหนด / ส่งผ่านโดยการอ้างอิง - คัดลอก" เสมอและจากนั้นคุณระบุ "กำหนดสำเนาของการอ้างอิงถึง x" ในกรณีของสิ่งที่คุณเรียกว่า "ค่าผสม" ค่าตัวแปรที่แท้จริงคือการอ้างอิง (เช่นตัวชี้หน่วยความจำ) เช่นเดียวกับที่คุณอธิบายการอ้างอิงจะถูกคัดลอก ... ดังนั้นค่าตัวแปรจะถูกคัดลอกอีกครั้งโดยเน้นว่าการอ้างอิงนั้นเป็นคุณค่า นั่นหมายความว่า JavaScript เป็นรหัสผ่านสำหรับทุกประเภท Pass-by-value หมายถึงการส่งสำเนาของค่าตัวแปร มันไม่สำคัญว่าค่านั้นจะเป็นการอ้างอิงถึงวัตถุ / อาร์เรย์
C Perkins

คุณแนะนำคำศัพท์ใหม่ (ค่าคัดลอก / อ้างอิง - คัดลอก) และนั่นทำให้สิ่งที่ซับซ้อนมากขึ้น มีเพียงแค่สำเนาระยะเวลา ถ้าคุณส่งแบบดั้งเดิมคุณผ่านการคัดลอกข้อมูลดั้งเดิมที่แท้จริงถ้าคุณผ่านวัตถุคุณผ่านการคัดลอกที่ตั้งหน่วยความจำของวัตถุ นั่นคือทั้งหมดที่คุณต้องพูด มีอะไรเพิ่มเติมที่สร้างความสับสนให้กับผู้คน
Scott Marcus

9

มันเกี่ยวกับ 'ประสิทธิภาพ' และ 'ความเร็ว' และใน 'การจัดการหน่วยความจำ' คำง่ายๆในภาษาการเขียนโปรแกรม

ในจาวาสคริปต์เราสามารถใส่ค่าลงในสองเลเยอร์: type1 - objectsและtype2 - ค่าประเภทอื่นทั้งหมดเช่นstring& boolean& ฯลฯ

ถ้าคุณจินตนาการถึงหน่วยความจำด้านล่างกำลังสองซึ่งในหนึ่งในนั้นมีค่า type2 เพียงค่าเดียวที่สามารถบันทึกได้:

ป้อนคำอธิบายรูปภาพที่นี่

ทุกค่า type2 (สีเขียว) เป็นรูปสี่เหลี่ยมจัตุรัสเดียวในขณะที่ค่า type1 (สีฟ้า) เป็นกลุ่มของพวกเขา :

ป้อนคำอธิบายรูปภาพที่นี่

ประเด็นก็คือถ้าคุณต้องการระบุค่า type2 ที่อยู่ธรรมดา แต่ถ้าคุณต้องการทำสิ่งเดียวกันสำหรับค่า type1 นั่นไม่ใช่เรื่องง่ายเลย! :

ป้อนคำอธิบายรูปภาพที่นี่

และในเรื่องที่ซับซ้อนมากขึ้น:

ป้อนคำอธิบายรูปภาพที่นี่

ดังนั้นที่นี่การอ้างอิงสามารถช่วยเรา: ป้อนคำอธิบายรูปภาพที่นี่

ในขณะที่ลูกศรสีเขียวที่นี่เป็นตัวแปรทั่วไปสีม่วงหนึ่งเป็นตัวแปรวัตถุดังนั้นเนื่องจากลูกศรสีเขียว (ตัวแปรทั่วไป) มีเพียงงานเดียว (และนั่นเป็นการระบุค่าทั่วไป) เราไม่จำเป็นต้องแยกค่ามันออกจาก ดังนั้นเราจึงย้ายลูกศรสีเขียวด้วยค่าของสิ่งที่มันไปและในการกำหนดฟังก์ชั่นและอื่น ๆ ...

แต่เราไม่สามารถทำสิ่งเดียวกันกับลูกศรสีม่วงเราอาจต้องการย้ายเซลล์ 'จอห์น' ที่นี่หรือสิ่งอื่น ๆ อีกมากมาย ... ดังนั้นลูกศรสีม่วงจะติดกับสถานที่และเพียงลูกศรทั่วไปที่ได้รับมอบหมายจะย้าย ...

สถานการณ์ที่สับสนมากคือที่คุณไม่สามารถรู้ได้ว่าการอ้างอิงตัวแปรของคุณเปลี่ยนแปลงอย่างไรลองมาดูตัวอย่างที่ดีมาก:

let arr = [1, 2, 3, 4, 5]; //arr is an object now and a purple arrow is indicating it
let obj2 = arr; // now, obj2 is another purple arrow that is indicating the value of arr obj
let obj3 = ['a', 'b', 'c'];
obj2.push(6); // first pic below - making a new hand for the blue circle to point the 6
//obj2 = [1, 2, 3, 4, 5, 6]
//arr = [1, 2, 3, 4, 5, 6]
//we changed the blue circle object value (type1-value) and due to arr and obj2 are indicating that so both of them changed
obj2 = obj3; //next pic below - changing the direction of obj2 array from blue circle to orange circle so obj2 is no more [1,2,3,4,5,6] and it's no more about changing anything in it but we completely changed its direction and now obj2 is pointing to obj3
//obj2 = ['a', 'b', 'c'];
//obj3 = ['a', 'b', 'c'];

ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่


8

นี่เป็นคำอธิบายเพิ่มเติมเล็กน้อยเกี่ยวกับการส่งผ่านค่าและการส่งผ่านอ้างอิง (JavaScript) ในแนวคิดนี้พวกเขากำลังพูดถึงการส่งผ่านตัวแปรโดยการอ้างอิงและผ่านตัวแปรโดยการอ้างอิง

ผ่านค่า (ชนิดดั้งเดิม)

var a = 3;
var b = a;

console.log(a); // a = 3
console.log(b); // b = 3

a=4;
console.log(a); // a = 4
console.log(b); // b = 3
  • นำไปใช้กับประเภทดั้งเดิมทั้งหมดใน JavaScript (สตริง, จำนวน, บูลีน, ไม่ได้กำหนดและเป็นโมฆะ)
  • a ถูกจัดสรรหน่วยความจำ (พูด 0x001) และ b สร้างสำเนาของค่าในหน่วยความจำ (พูด 0x002)
  • ดังนั้นการเปลี่ยนค่าของตัวแปรจึงไม่ส่งผลกระทบต่อสิ่งอื่นเนื่องจากทั้งคู่อยู่ในตำแหน่งที่ต่างกันสองแห่ง

ผ่านการอ้างอิง (วัตถุ)

var c = { "name" : "john" };
var d = c;

console.log(c); // { "name" : "john" }
console.log(d); // { "name" : "john" }

c.name = "doe";

console.log(c); // { "name" : "doe" }
console.log(d); // { "name" : "doe" }
  • cเอ็นจิ้นJavaScript กำหนดวัตถุให้กับตัวแปรและชี้ไปยังหน่วยความจำบางส่วนแล้วบอกว่า (0x012)
  • เมื่อ d = c ในขั้นตอนนี้dชี้ไปที่ตำแหน่งเดียวกัน (0x012)
  • การเปลี่ยนค่าของค่าการเปลี่ยนแปลงใด ๆ สำหรับทั้งตัวแปร
  • ฟังก์ชั่นเป็นวัตถุ

กรณีพิเศษ, ผ่านการอ้างอิง (วัตถุ)

c = {"name" : "jane"};
console.log(c); // { "name" : "jane" }
console.log(d); // { "name" : "doe" }
  • ตัวดำเนินการเท่ากับ (=) ตั้งค่าพื้นที่หน่วยความจำหรือที่อยู่ใหม่

ในกรณีพิเศษที่คุณเรียกว่าไม่ใช่ตัวดำเนินการกำหนดค่าที่ทำให้การจัดสรรพื้นที่หน่วยความจำเป็นวัตถุตามตัวอักษร เครื่องหมายวงเล็บเคอร์เลย์ทำให้การสร้างวัตถุใหม่ คุณสมบัติcถูกตั้งค่าเป็นสำเนาของการอ้างอิงของวัตถุใหม่
georgeawg

6

แบ่งปันสิ่งที่ฉันรู้เกี่ยวกับการอ้างอิงใน JavaScript

ใน JavaScript เมื่อกำหนดวัตถุให้กับตัวแปรค่าที่กำหนดให้กับตัวแปรนั้นเป็นการอ้างอิงไปยังวัตถุ:

var a = {
  a: 1,
  b: 2,
  c: 3
};
var b = a;

// b.c is referencing to a.c value
console.log(b.c) // Output: 3
// Changing value of b.c
b.c = 4
// Also changes the value of a.c
console.log(a.c) // Output: 4


1
นี่เป็นคำตอบที่ง่ายเกินไปซึ่งไม่ได้บอกว่าคำตอบก่อนหน้านี้ยังไม่ได้อธิบายที่ดีกว่า ฉันสับสนเกี่ยวกับสาเหตุที่คุณเรียกใช้อาร์เรย์เป็นกรณีพิเศษ
เควนติน

1
" วัตถุถูกเก็บไว้เป็นข้อมูลอ้างอิง " กำลังทำให้เข้าใจผิด สิ่งที่ฉันคิดว่าคุณหมายถึงคือเมื่อกำหนดวัตถุให้กับตัวแปรค่าที่กำหนดให้กับตัวแปรนั้นเป็นการอ้างอิงไปยังวัตถุ
RobG

สิ่งนี้ไม่ได้แก้ไขปัญหาของการอัปเดตวัตถุภายในฟังก์ชั่นที่ไม่ได้อัปเดตวัตถุนอกฟังก์ชั่น นั่นคือภาพรวมที่ดูเหมือนว่าจะทำงานเป็นค่าแทนที่จะเป็นข้อมูลอ้างอิง ดังนั้น -1
amaster

@amaster ขอบคุณที่ชี้ให้เห็น! คุณช่วยแนะนำการแก้ไขได้ไหม
xameeramir

ฮ่าฮ่าฉันลอง ... การแก้ไขที่แนะนำของฉันเปลี่ยนไปมากเกินไปไม่ได้รับอนุญาต
amaster

4

ความหมาย !! การตั้งคำจำกัดความที่เป็นรูปธรรมนั้นจำเป็นต้องทำให้คำตอบและความเห็นที่เข้ากันไม่ได้เนื่องจากพวกเขาไม่ได้อธิบายสิ่งเดียวกันแม้ว่าจะใช้คำและวลีเดียวกัน แต่จำเป็นอย่างยิ่งที่จะต้องผ่านความสับสน

ประการแรกมีหลายระดับของนามธรรมที่ทุกคนดูเหมือนจะไม่เข้าใจ โปรแกรมเมอร์รุ่นใหม่ที่เรียนรู้ภาษาที่ 4 หรือ 5 อาจมีปัญหาในการเข้าใจแนวคิดที่คุ้นเคยกับแอสเซมบลีหรือโปรแกรมเมอร์ C ที่ไม่แนะนำให้ชี้ไปที่พอยน์เตอร์ Pass-by-Reference ไม่ได้หมายถึงความสามารถในการเปลี่ยนวัตถุอ้างอิงโดยใช้ตัวแปรพารามิเตอร์ฟังก์ชั่น

ตัวแปร : แนวคิดแบบรวมของสัญลักษณ์ที่อ้างอิงค่าที่ตำแหน่งเฉพาะในหน่วยความจำ คำนี้มักจะโหลดเกินไปที่จะใช้เพียงอย่างเดียวในการพูดคุยรายละเอียด

สัญลักษณ์ : สตริงข้อความที่ใช้เพื่ออ้างถึงตัวแปร (เช่นชื่อตัวแปร)

ค่า : บิตเฉพาะที่เก็บไว้ในหน่วยความจำและอ้างอิงโดยใช้สัญลักษณ์ของตัวแปร

ตำแหน่งหน่วยความจำ : ที่เก็บค่าของตัวแปร (สถานที่นั้นจะถูกแทนด้วยตัวเลขที่แยกจากค่าที่เก็บไว้ในสถานที่นั้น)

พารามิเตอร์ฟังก์ชัน : ตัวแปรที่ประกาศในนิยามฟังก์ชันใช้สำหรับอ้างอิงตัวแปรที่ส่งผ่านไปยังฟังก์ชัน

อาร์กิวเมนต์ของฟังก์ชัน : ตัวแปรภายนอกฟังก์ชันซึ่งส่งผ่านไปยังฟังก์ชันโดยผู้เรียก

ตัวแปรวัตถุ : ตัวแปรที่มีค่าพื้นฐานไม่ได้เป็น "วัตถุ" ตัวเอง แต่ค่าของมันคือตัวชี้ (ค่าตำแหน่งหน่วยความจำ) ไปยังตำแหน่งอื่นในหน่วยความจำที่เก็บข้อมูลจริงของวัตถุ ในภาษาที่สูงกว่ารุ่นส่วนใหญ่ "ตัวชี้" จะถูกซ่อนอย่างมีประสิทธิภาพโดยการยกเลิกการอ้างอิงอัตโนมัติในบริบทต่างๆ

ตัวแปรดั้งเดิม : ตัวแปรที่มีค่าเป็นค่าจริง แม้แนวคิดนี้อาจซับซ้อนโดยอัตโนมัติ - มวยและบริบทเหมือนวัตถุของภาษาต่าง ๆ แต่ความคิดทั่วไปคือค่าของตัวแปรคือมูลค่าที่แท้จริงที่แสดงโดยสัญลักษณ์ของตัวแปรมากกว่าตัวชี้ไปยังตำแหน่งหน่วยความจำอื่น

อาร์กิวเมนต์ของฟังก์ชันและพารามิเตอร์ไม่เหมือนกัน นอกจากนี้ค่าของตัวแปรไม่ได้เป็นวัตถุของตัวแปร (ตามที่ชี้ไปแล้วโดยคนต่าง ๆ แต่ไม่สนใจอย่างชัดเจน) ความแตกต่างเหล่านี้มีความสำคัญต่อความเข้าใจที่เหมาะสม

Pass-by-value หรือ Call-by-sharing (สำหรับวัตถุ): ค่าของอาร์กิวเมนต์ของฟังก์ชันคือ COPIED ไปยังตำแหน่งหน่วยความจำอื่นซึ่งอ้างอิงโดยสัญลักษณ์พารามิเตอร์ของฟังก์ชัน (ไม่ว่าจะอยู่ในสแต็กหรือฮีป) กล่าวอีกนัยหนึ่งพารามิเตอร์ฟังก์ชั่นได้รับสำเนาของค่าของอาร์กิวเมนต์ที่ส่งผ่าน ... และ (สำคัญ) ค่าของอาร์กิวเมนต์ไม่เคยถูกอัปเดต / เปลี่ยนแปลง / เปลี่ยนแปลงโดยฟังก์ชันการเรียก โปรดจำไว้ว่าค่าของตัวแปรวัตถุไม่ใช่วัตถุ แต่เป็นตัวชี้ไปยังวัตถุดังนั้นการส่งผ่านตัวแปรวัตถุโดยค่าจะคัดลอกตัวชี้ไปยังตัวแปรพารามิเตอร์ของฟังก์ชัน ค่าพารามิเตอร์ของฟังก์ชันชี้ไปที่วัตถุเดียวกันที่แน่นอนในหน่วยความจำ ข้อมูลวัตถุเองสามารถเปลี่ยนแปลงได้โดยตรงผ่านพารามิเตอร์ฟังก์ชั่น แต่ค่าของฟังก์ชั่นอาร์กิวเมนต์ไม่เคยอัพเดทดังนั้นมันจะยังคงชี้ไปที่เดียวกันวัตถุตลอดและแม้กระทั่งหลังจากการเรียกใช้ฟังก์ชัน (แม้ว่าข้อมูลของวัตถุจะถูกเปลี่ยนแปลงหรือถ้าพารามิเตอร์ฟังก์ชั่นได้รับมอบหมายวัตถุที่แตกต่างกันทั้งหมด) มันไม่ถูกต้องที่จะสรุปว่าอาร์กิวเมนต์ของฟังก์ชั่นถูกส่งผ่านโดยการอ้างอิงเพียงเพราะวัตถุที่อ้างอิงนั้นสามารถอัปเดตได้ผ่านตัวแปรพารามิเตอร์ฟังก์ชั่น

โทร / ผ่านอ้างอิง : ค่าของฟังก์ชั่นอาร์กิวเมนต์สามารถ / จะถูกปรับปรุงโดยตรงโดยพารามิเตอร์ฟังก์ชั่นที่สอดคล้องกัน หากช่วยได้พารามิเตอร์ฟังก์ชันจะกลายเป็น "นามแฝง" ที่มีประสิทธิภาพสำหรับอาร์กิวเมนต์ซึ่งจะอ้างถึงค่าเดียวกันที่ตำแหน่งหน่วยความจำเดียวกัน หากอาร์กิวเมนต์ของฟังก์ชันเป็นตัวแปรของวัตถุความสามารถในการเปลี่ยนข้อมูลของวัตถุจะไม่แตกต่างจากตัวพิมพ์ใหญ่ - เล็กเนื่องจากค่าฟังก์ชันจะยังคงชี้ไปที่วัตถุเดียวกันกับอาร์กิวเมนต์ แต่ในกรณีที่วัตถุตัวแปรถ้าพารามิเตอร์ฟังก์ชั่นการตั้งค่าเป็นวัตถุที่แตกต่างกันโดยสิ้นเชิงแล้วอาร์กิวเมนต์ก็จะชี้ไปยังวัตถุที่แตกต่างกัน - เช่นนี้จะไม่เกิดขึ้นในกรณีที่ผ่านค่า

JavaScript ไม่ผ่านการอ้างอิง หากคุณอ่านอย่างใกล้ชิดคุณจะตระหนักว่าความคิดเห็นที่ขัดแย้งกันทั้งหมดเข้าใจผิดว่าอะไรคือความหมายของการส่งผ่านค่าและพวกเขาสรุปว่าความสามารถในการอัปเดตข้อมูลของวัตถุผ่านพารามิเตอร์ฟังก์ชั่นนั้นมีความหมายเหมือนกัน

การคัดลอก / คัดลอกวัตถุ: มีการสร้างวัตถุใหม่และคัดลอกข้อมูลของวัตถุต้นฉบับ นี่อาจเป็นสำเนาลึกหรือสำเนาตื้น ๆ แต่ประเด็นคือวัตถุใหม่ถูกสร้างขึ้น การสร้างสำเนาของวัตถุเป็นแนวคิดแยกต่างหากจากการส่งผ่านค่า บางภาษาแยกความแตกต่างระหว่างคลาสอ็อบเจ็กต์และ structs (หรือคล้ายกัน) และอาจมีพฤติกรรมที่แตกต่างกันสำหรับการส่งผ่านตัวแปรประเภทต่าง ๆ แต่จาวาสคริปต์จะไม่ทำสิ่งนี้โดยอัตโนมัติเมื่อผ่านตัวแปรออบเจ็กต์ แต่การไม่มีการโคลนวัตถุอัตโนมัติไม่ได้แปลเป็นการอ้างอิงแบบอ้างอิง


4

จาวาสคริปต์ผ่านประเภทดั้งเดิมตามค่าและประเภทวัตถุโดยการอ้างอิง

ตอนนี้ผู้คนชอบทะเลาะกันอย่างไม่รู้จบว่า "pass by reference" เป็นวิธีที่ถูกต้องในการอธิบายสิ่งที่ Java และคณะ ทำจริง ประเด็นคือ:

  1. ผ่านวัตถุไม่ได้คัดลอกวัตถุ
  2. วัตถุที่ถูกส่งผ่านไปยังฟังก์ชั่นสามารถมีสมาชิกของมันถูกแก้ไขโดยฟังก์ชั่น
  3. ค่าดั้งเดิมที่ส่งไปยังฟังก์ชันไม่สามารถแก้ไขได้โดยฟังก์ชัน ทำสำเนา

ในหนังสือของฉันที่เรียกว่าผ่านการอ้างอิง

- Brian Bi - ภาษาการเขียนโปรแกรมใดที่ผ่านการอ้างอิง?


ปรับปรุง

นี่คือข้อโต้แย้งกับสิ่งนี้:

ไม่มี "การส่งผ่านอ้างอิง" ใน JavaScript


@Amy เพราะนั่นคือการอธิบายถึงการผ่านค่าไม่ผ่านการอ้างอิง คำตอบนี้เป็นคำตอบที่ดีที่แสดงความแตกต่าง: stackoverflow.com/a/3638034/3307720
nasch

@nasch ฉันเข้าใจความแตกต่าง # 1 และ # 2 กำลังอธิบายความหมายแบบอ้างอิงทีละตัว # 3 กำลังอธิบายความหมายของ pass-by-value
Amy

@Amy 1, 2 และ 3 ทั้งหมดสอดคล้องกับ pass by value หากต้องการผ่านการอ้างอิงคุณจะต้อง 4: การกำหนดการอ้างอิงให้กับค่าใหม่ภายในฟังก์ชั่น (ด้วยตัวดำเนินการ =) ยังกำหนดการอ้างอิงภายนอกฟังก์ชันอีกครั้ง นี่ไม่ใช่กรณีที่มี Javascript ทำให้ผ่านค่าเฉพาะ เมื่อผ่านวัตถุคุณส่งตัวชี้ไปยังวัตถุและคุณผ่านตัวชี้นั้นตามค่า
nasch

โดยทั่วไปไม่ได้หมายความว่า "การส่งต่ออ้างอิง" คุณพอใจคำค้นหาของฉันและฉันไม่เห็นด้วยกับคุณ ขอบคุณ
Amy

"ในหนังสือของฉันที่เรียกว่าผ่านการอ้างอิง" - ในหนังสือคอมไพเลอร์ทุกเล่มหนังสือล่ามหนังสือทฤษฎีการเขียนโปรแกรมภาษาและหนังสือวิทยาศาสตร์คอมพิวเตอร์ที่ไม่เคยเขียนมาก่อน
Jörg W Mittag

3

วิธีง่ายๆในการเข้าใจสิ่งนี้ ...

  • เมื่อเรียกใช้ฟังก์ชันคุณกำลังส่งเนื้อหา (การอ้างอิงหรือค่า) ของตัวแปรอาร์กิวเมนต์ไม่ใช่ตัวแปรเอง

    var var1 = 13;
    var var2 = { prop: 2 };
    
    //13 and var2's content (reference) are being passed here
    foo(var1, var2); 
  • ภายในฟังก์ชั่นตัวแปรพารามิเตอร์inVar1และinVar2รับเนื้อหาที่ถูกส่งผ่าน

    function foo(inVar1, inVar2){
        //changing contents of inVar1 and inVar2 won't affect variables outside
        inVar1 = 20;
        inVar2 = { prop: 7 };
    }
  • ตั้งแต่inVar2ได้รับการอ้างอิง{ prop: 2 }คุณสามารถเปลี่ยนค่าของคุณสมบัติของวัตถุ

    function foo(inVar1, inVar2){
        inVar2.prop = 7; 
    }

3

การส่งอาร์กิวเมนต์ไปยังฟังก์ชันใน JavaScript นั้นคล้ายคลึงกับพารามิเตอร์การส่งผ่านตามค่าตัวชี้ใน C:

/*
The following C program demonstrates how arguments
to JavaScript functions are passed in a way analogous
to pass-by-pointer-value in C. The original JavaScript
test case by @Shog9 follows with the translation of
the code into C. This should make things clear to
those transitioning from C to JavaScript.

function changeStuff(num, obj1, obj2)
{
    num = num * 10;
    obj1.item = "changed";
    obj2 = {item: "changed"};
}

var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};
changeStuff(num, obj1, obj2);
console.log(num);
console.log(obj1.item);    
console.log(obj2.item);

This produces the output:

10
changed
unchanged
*/

#include <stdio.h>
#include <stdlib.h>

struct obj {
    char *item;
};

void changeStuff(int *num, struct obj *obj1, struct obj *obj2)
{
    // make pointer point to a new memory location
    // holding the new integer value
    int *old_num = num;
    num = malloc(sizeof(int));
    *num = *old_num * 10;
    // make property of structure pointed to by pointer
    // point to the new value
    obj1->item = "changed";
    // make pointer point to a new memory location
    // holding the new structure value
    obj2 = malloc(sizeof(struct obj));
    obj2->item = "changed";
    free(num); // end of scope
    free(obj2); // end of scope
}

int num = 10;
struct obj obj1 = { "unchanged" };
struct obj obj2 = { "unchanged" };

int main()
{
    // pass pointers by value: the pointers
    // will be copied into the argument list
    // of the called function and the copied
    // pointers will point to the same values
    // as the original pointers
    changeStuff(&num, &obj1, &obj2);
    printf("%d\n", num);
    puts(obj1.item);
    puts(obj2.item);
    return 0;
}

1
ฉันไม่คิดว่าเป็นเช่นนี้ใน JavaScript: `` `javascript var num = 5;
Danail Nachev

@DanailNachev: ในขณะที่อาจเป็นจริงในทางเทคนิคความแตกต่างสามารถสังเกตได้เฉพาะสำหรับวัตถุที่ไม่แน่นอนซึ่ง ECMAScript ดั้งเดิมไม่ได้
Jörg W Mittag

3

สำหรับนักกฎหมายด้านการเขียนโปรแกรมฉันได้อ่านหัวข้อต่อไปนี้ของ ECMAScript 5.1 (ซึ่งง่ายต่อการอ่านมากกว่าฉบับล่าสุด) และไปไกลเท่าที่ถามในรายการส่งจดหมาย ECMAScript

TL; DR : ทุกสิ่งที่ผ่านไปตามค่า แต่คุณสมบัติของออบเจ็กต์คือการอ้างอิงและคำจำกัดความของออบเจ็กต์ขาดมาตรฐานอย่างน่าขนลุก

การสร้างรายการอาร์กิวเมนต์

ส่วนที่ 11.2.4 "รายการอาร์กิวเมนต์" กล่าวว่าสิ่งต่อไปนี้ในการสร้างรายการอาร์กิวเมนต์ประกอบด้วยอาร์กิวเมนต์ 1 ข้อเท่านั้น:

ArgumentList ที่ใช้งานจริง: Assignment Expression ถูกประเมินดังนี้:

  1. ให้ ref เป็นผลของการประเมิน Assignment Expression
  2. ปล่อยให้ arg เป็น GetValue (อ้างอิง)
  3. ส่งคืนรายการที่มีเพียงรายการเดียวที่มีอาร์กิวเมนต์

ส่วนยังระบุกรณีที่รายการอาร์กิวเมนต์มีอาร์กิวเมนต์ 0 หรือ> 1

ดังนั้นทุกอย่างจะถูกส่งผ่านโดยการอ้างอิง

การเข้าถึงคุณสมบัติของวัตถุ

ส่วนที่ 11.2.1 "ผู้เข้าถึงอสังหาริมทรัพย์"

การผลิต MemberExpression: Member Expression [Expression] ได้รับการประเมินดังนี้:

  1. ให้ baseReference เป็นผลลัพธ์ของการประเมิน MemberExpression
  2. ให้ baseValue เป็น GetValue (baseReference)
  3. ให้ propertyNameReference เป็นผลลัพธ์ของการประเมิน Expression
  4. ให้ propertyNameValue เป็น GetValue (propertyNameReference)
  5. โทร CheckObjectCoercible (baseValue)
  6. ให้ propertyNameString เป็น ToString (propertyNameValue)
  7. หากการผลิตวากยสัมพันธ์ที่ถูกประเมินอยู่ในรหัสโหมดเข้มงวดให้เข้มงวดเป็นจริงอื่น ๆ ให้เข้มงวดเป็นเท็จ
  8. ส่งคืนค่าประเภทการอ้างอิงที่มีค่าฐานคือ baseValue และชื่อที่อ้างอิงคือ propertyNameString และการตั้งค่าสถานะโหมดเข้มงวดนั้นเข้มงวด

ดังนั้นคุณสมบัติของออบเจกต์จึงมีอยู่เสมอเพื่อใช้อ้างอิง

ในการอ้างอิง

มีการอธิบายไว้ในหัวข้อ 8.7 "ประเภทข้อมูลจำเพาะอ้างอิง" ซึ่งการอ้างอิงนั้นไม่ใช่ประเภทจริงในภาษา - พวกมันใช้เพื่ออธิบายพฤติกรรมของการลบประเภทของและตัวดำเนินการที่ได้รับมอบหมายเท่านั้น

คำจำกัดความของ "วัตถุ"

มันถูกกำหนดในรุ่น 5.1 ว่า "วัตถุคือชุดของคุณสมบัติ" ดังนั้นเราสามารถอนุมานได้ว่าคุณค่าของวัตถุคือการรวบรวม แต่สิ่งที่เป็นค่าของการเก็บรวบรวมที่กำหนดไว้ไม่ดีในสเป็คและต้องการความพยายามที่จะเข้าใจ


ไม่เคยทำให้ฉันประหลาดใจเลยว่ามีกี่คนที่สับสนระหว่างความแตกต่างระหว่างข้อโต้แย้งที่ส่งผ่านโดยค่าการขัดแย้งที่ส่งผ่านโดยการอ้างอิงการดำเนินการกับวัตถุทั้งหมดและการดำเนินการกับคุณสมบัติของพวกเขา ในปีค. ศ. 1979 ฉันไม่ได้รับปริญญาด้านวิทยาศาสตร์คอมพิวเตอร์โดยเลือกที่จะเพิ่มวิชาเลือก CS 15 ชั่วโมงหรือมากกว่านั้นในหลักสูตร MBA ของฉัน อย่างไรก็ตามในไม่ช้ามันก็กลายเป็นที่ชัดเจนสำหรับฉันที่เข้าใจแนวคิดเหล่านี้อย่างน้อยก็ดีเท่าที่เพื่อนร่วมงานของฉันคนหนึ่งซึ่งมีปริญญาในสาขาวิทยาศาสตร์คอมพิวเตอร์หรือคณิตศาสตร์ ศึกษา Assembler และมันจะกลายเป็นค่อนข้างชัดเจน
David A. Grey

3

เอกสาร MDN อธิบายอย่างชัดเจนโดยไม่ต้องละเอียดเกินไป:

พารามิเตอร์ของสายฟังก์ชั่นที่มีฟังก์ชั่นของการขัดแย้ง อาร์กิวเมนต์จะมีการส่งผ่านไปยังฟังก์ชั่นด้วยค่า หากฟังก์ชั่นเปลี่ยนค่าของข้อโต้แย้งการเปลี่ยนแปลงนี้จะไม่สะท้อนไปทั่วโลกหรือในฟังก์ชั่นการโทร อย่างไรก็ตามการอ้างอิงวัตถุก็มีค่าเช่นกันและเป็นพิเศษ: หากฟังก์ชันเปลี่ยนคุณสมบัติของวัตถุที่อ้างถึงการเปลี่ยนแปลงนั้นจะมองเห็นได้นอกฟังก์ชั่น (... )

ที่มา: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Description


1

ในภาษาระดับต่ำหากคุณต้องการส่งผ่านตัวแปรโดยการอ้างอิงคุณต้องใช้ไวยากรณ์เฉพาะในการสร้างฟังก์ชั่น:

int myAge = 14;
increaseAgeByRef(myAge);
function increaseAgeByRef(int &age) {
  *age = *age + 1;
}

&ageคือการอ้างอิงถึงแต่ถ้าคุณต้องการค่าที่คุณต้องแปลงอ้างอิงที่ใช้myAge*age

Javascript เป็นภาษาระดับสูงที่ทำหน้าที่นี้ให้คุณ ดังนั้นแม้ว่าวัตถุจะถูกส่งผ่านโดยการอ้างอิงภาษาจะแปลงพารามิเตอร์การอ้างอิงเป็นค่า คุณไม่จำเป็นต้องใช้&คำจำกัดความของฟังก์ชั่นในการส่งต่อโดยอ้างอิงทั้ง*ในส่วนของฟังก์ชั่นการแปลงค่าอ้างอิงเป็นค่า JS ทำเพื่อคุณ

นั่นเป็นเหตุผลที่เมื่อคุณพยายามที่จะเปลี่ยนวัตถุภายในฟังก์ชั่นโดยการแทนที่ค่าของมัน (เช่นage = {value:5}) การเปลี่ยนแปลงไม่ได้มีอยู่ แต่ถ้าคุณเปลี่ยนคุณสมบัติของมัน (เช่นage.value = 5) มันจะทำ

เรียนรู้เพิ่มเติม


1

ฉันได้อ่านคำตอบเหล่านี้หลายครั้ง แต่ไม่ได้จนกว่าจะได้เรียนรู้เกี่ยวกับคำจำกัดความทางเทคนิคของ"การโทรด้วยการแบ่งปัน"ตามที่ Barbara Liskov เรียกว่า

ซีแมนทิกส์ของการโทรโดยการแชร์แตกต่างจากการโทรโดยการอ้างอิงในการมอบหมายให้อาร์กิวเมนต์ของฟังก์ชันภายในฟังก์ชันไม่สามารถมองเห็นผู้โทรได้ (ไม่เหมือนกับซีแมนทิกส์อ้างอิง) [อ้างอิงที่จำเป็น] ดังนั้นเช่นถ้าตัวแปรถูกส่งผ่าน เพื่อจำลองการมอบหมายงานให้กับตัวแปรนั้นในขอบเขตของผู้โทร อย่างไรก็ตามเนื่องจากฟังก์ชั่นมีการเข้าถึงวัตถุเดียวกันกับผู้โทร (ไม่มีการคัดลอก) การกลายพันธุ์ของวัตถุเหล่านั้นหากวัตถุไม่แน่นอนในการทำงานภายในฟังก์ชั่นจะมองเห็นผู้โทรซึ่งอาจปรากฏแตกต่างจากค่าโทร อรรถศาสตร์ การเปลี่ยนแปลงของวัตถุที่ไม่แน่นอนภายในฟังก์ชั่นสามารถมองเห็นได้โดยผู้โทรเพราะวัตถุไม่ได้คัดลอกหรือลอกแบบ - มันถูกแชร์

นั่นคือการอ้างอิงพารามิเตอร์สามารถเปลี่ยนแปลงได้หากคุณไปและเข้าถึงค่าพารามิเตอร์เอง ในทางกลับกันการกำหนดให้กับพารามิเตอร์จะหายไปหลังจากการประเมินผลและไม่สามารถเรียกใช้ฟังก์ชันได้


ไม่ไม่ว่าจะเป็นวัตถุที่ไม่แน่นอนหรือไม่ไม่ใช่ปัญหา ทุกสิ่งถูกส่งผ่านตามค่าเสมอ มันขึ้นอยู่กับสิ่งที่คุณผ่าน (ค่าหรือการอ้างอิง) ดูนี่สิ
Scott Marcus

สิ่งที่เธออธิบายกำลังส่งผ่านการอ้างอิง BY-VALUE ไม่มีเหตุผลที่จะแนะนำคำศัพท์ใหม่
Sanjeev

1

วิธีที่ง่ายที่สุดส่วนใหญ่

// Copy JS object without reference
var first = {"a":"value1","b":"value2"};
var clone = JSON.parse( JSON.stringify( first ) ); 

var second = ["a","b","c"];
var clone = JSON.parse( JSON.stringify( second ) ); 

JSON.parse( JSON.stringify( obj ) )เป็นวิธีที่น่ากลัวสำหรับวัตถุโคลนลึก ไม่เพียง แต่ไม่เพียงช้า แต่ยังอาจทำให้ข้อมูลสูญหายได้
D. Pardal

0

ฉันพบวิธีการขยายของไลบรารี Underscore.jsมีประโยชน์มากเมื่อฉันต้องการส่งผ่านวัตถุเป็นพารามิเตอร์ซึ่งอาจถูกปรับเปลี่ยนหรือแทนที่ทั้งหมด

function replaceOrModify(aObj) {
  if (modify) {

    aObj.setNewValue('foo');

  } else {

   var newObj = new MyObject();
   // _.extend(destination, *sources) 
   _.extend(newObj, aObj);
  }
}

0

คำอธิบายที่รวบรัดที่สุดที่ฉันพบในคู่มือสไตล์ AirBNB :

  • ดึกดำบรรพ์ : เมื่อคุณเข้าถึงประเภทดั้งเดิมคุณจะทำงานโดยตรงกับคุณค่าของมัน

    • เชือก
    • จำนวน
    • บูล
    • โมฆะ
    • ไม่ได้กำหนด

เช่น:

var foo = 1,
    bar = foo;

bar = 9;

console.log(foo, bar); // => 1, 9
  • ซับซ้อน : เมื่อคุณเข้าถึงชนิดที่ซับซ้อนคุณทำงานกับการอ้างอิงถึงค่าของมัน

    • วัตถุ
    • แถว
    • ฟังก์ชัน

เช่น:

var foo = [1, 2],
    bar = foo;

bar[0] = 9;

console.log(foo[0], bar[0]); // => 9, 9

คือประเภทดั้งเดิมได้อย่างมีประสิทธิภาพจะถูกส่งผ่านโดยค่าและประเภทที่ซับซ้อนจะถูกส่งผ่านโดยการอ้างอิง


ไม่ทุกอย่างถูกส่งผ่านตามค่าเสมอ มันขึ้นอยู่กับสิ่งที่คุณผ่าน (ค่าหรือการอ้างอิง) ดูนี่สิ
Scott Marcus

-1

ฉันจะบอกว่ามันเป็นแบบผ่านสำเนา -

พิจารณาข้อโต้แย้งและวัตถุตัวแปรเป็นวัตถุที่สร้างขึ้นในระหว่างการดำเนินการบริบทที่สร้างขึ้นในจุดเริ่มต้นของฟังก์ชั่นการร้องขอ - และมูลค่าที่แท้จริง / การอ้างอิงของคุณส่งผ่านไปยังฟังก์ชั่นเพียงแค่เก็บไว้ในอาร์กิวเมนต์นี้ + วัตถุตัวแปร

เพียงแค่พูดสำหรับประเภทดั้งเดิมค่าได้รับการคัดลอกในการเริ่มฟังก์ชั่นสำหรับประเภทวัตถุการอ้างอิงได้รับการคัดลอก


1
"pass-by-copy" === ผ่านค่า
Scott Marcus

-1

มีการสนทนาบางอย่างเกี่ยวกับการใช้คำว่า "ส่งต่ออ้างอิง" ใน JavaScript ที่นี่แต่เพื่อตอบคำถามของคุณ:

วัตถุจะถูกส่งโดยอัตโนมัติโดยการอ้างอิงโดยไม่จำเป็นต้องระบุเป็นพิเศษ

(จากบทความที่กล่าวถึงข้างต้น)


7
บทความที่เชื่อมโยงไม่รวมคำแถลงนั้นและหลีกเลี่ยงการใช้ "ส่งต่ออ้างอิง" โดยสิ้นเชิง
C Perkins

ค่านี้เป็นข้อมูลอ้างอิง

-2

วิธีง่ายๆในการพิจารณาว่ามีบางสิ่งบางอย่าง "ผ่านการอ้างอิง" คือคุณสามารถเขียนฟังก์ชั่น "สลับ" ตัวอย่างเช่นใน C คุณสามารถทำได้:

void swap(int *i, int *j)
{
    int t;
    t = *i;
    *i = *j;
    *j = t;
}

หากคุณไม่สามารถทำสิ่งนั้นได้ใน JavaScript มันจะไม่ "ส่งต่อโดยอ้างอิง"


21
สิ่งนี้ไม่ได้ผ่านการอ้างอิงจริงๆ คุณกำลังส่งพอยน์เตอร์ไปยังฟังก์ชันและพอยน์เตอร์เหล่านั้นจะถูกส่งผ่านตามค่า ตัวอย่างที่ดีกว่าคือคำว่า & โอเปอเรเตอร์ของ C ++ หรือคีย์เวิร์ด "ref" ของ C # ซึ่งทั้งคู่นั้นผ่านการอ้างอิงอย่างแท้จริง
Matt Greer

ยิ่งไปกว่านั้นคือทุกสิ่งถูกส่งผ่านโดยค่าใน JavaScript
Scott Marcus


-3
  1. ตัวแปรชนิดดั้งเดิมเช่นสตริงจำนวนจะถูกส่งผ่านตามค่าเสมอ
  2. อาร์เรย์และวัตถุถูกส่งผ่านตามการอ้างอิงหรือการส่งผ่านตามค่าโดยยึดตามเงื่อนไขสองข้อนี้

    • หากคุณกำลังเปลี่ยนค่าของวัตถุหรืออาร์เรย์นั้นด้วย Object หรือ Array ใหม่มันจะถูกส่งผ่านตามค่า

      object1 = {item: "car"}; array1=[1,2,3];

    ที่นี่คุณกำลังกำหนดวัตถุหรืออาร์เรย์ใหม่ให้แก่ one.you คุณไม่ได้เปลี่ยนมูลค่าของทรัพย์สินของวัตถุเก่าดังนั้นมันจะผ่านค่า

    • ถ้าคุณกำลังเปลี่ยนค่าคุณสมบัติของวัตถุหรืออาร์เรย์มันจะผ่านการอ้างอิง

      object1.key1= "car"; array1[0]=9;

    ที่นี่คุณกำลังเปลี่ยนค่าคุณสมบัติของวัตถุเก่าคุณไม่ได้กำหนดวัตถุหรืออาร์เรย์ใหม่ให้แก่วัตถุเก่าดังนั้นมันจึงผ่านการอ้างอิง

รหัส

    function passVar(object1, object2, number1) {

        object1.key1= "laptop";
        object2 = {
            key2: "computer"
        };
        number1 = number1 + 1;
    }

    var object1 = {
        key1: "car"
    };
    var object2 = {
        key2: "bike"
    };
    var number1 = 10;

    passVar(object1, object2, number1);
    console.log(object1.key1);
    console.log(object2.key2);
    console.log(number1);

Output: -
    laptop
    bike
    10

1
ผู้ประกอบการที่ได้รับมอบหมายต้องไม่สับสนกับการเรียกใช้ฟังก์ชัน เมื่อคุณกำหนดข้อมูลใหม่ให้กับตัวแปรที่มีอยู่จำนวนการอ้างอิงของข้อมูลเก่าจะลดลงและข้อมูลใหม่จะเชื่อมโยงกับตัวแปรเก่า โดยพื้นฐานแล้วตัวแปรจะชี้ไปที่ข้อมูลใหม่ เช่นเดียวกับตัวแปรคุณสมบัติ เนื่องจากการมอบหมายเหล่านี้ไม่ใช่การเรียกใช้ฟังก์ชันพวกเขาไม่มีส่วนเกี่ยวข้องกับ pass-by-value หรือ pass-by-reference
John Sonderson

1
ไม่ทุกอย่างถูกส่งผ่านตามค่าเสมอ มันขึ้นอยู่กับสิ่งที่คุณผ่าน (ค่าหรือการอ้างอิง) ดูนี่สิ
Scott Marcus

-3
  1. Primitives (number, Boolean, ฯลฯ ) ถูกส่งผ่านโดยค่า
    • เงื่อนไขไม่เปลี่ยนรูปดังนั้นมันจึงไม่สำคัญสำหรับพวกเขา
  2. วัตถุถูกส่งผ่านโดยการอ้างอิง (การอ้างอิงถูกส่งผ่านตามค่า)

ไม่ทุกอย่างถูกส่งผ่านตามค่าเสมอ มันขึ้นอยู่กับสิ่งที่คุณผ่าน (ค่าหรือการอ้างอิง) ดูนี่สิ
Scott Marcus

คำสั่งที่สองของคุณขัดแย้งกับตัวเอง
Jörg W Mittag

-5

ค่าง่าย ๆ ภายในฟังก์ชั่นจะไม่เปลี่ยนค่าเหล่านั้นนอกฟังก์ชั่น (ถูกส่งผ่านโดยค่า) ในขณะที่ค่าที่ซับซ้อนจะถูก (ผ่านการอ้างอิง)

function willNotChange(x) {

    x = 1;
}

var x = 1000;

willNotChange(x);

document.write('After function call, x = ' + x + '<br>'); // Still 1000

function willChange(y) {

    y.num = 2;
}

var y = {num: 2000};

willChange(y);
document.write('After function call y.num = ' + y.num + '<br>'); // Now 2, not 2000

นั่นไร้สาระ y จะเปลี่ยนแปลงเนื่องจากขอบเขตการทำงานระดับมันไม่ถูกยกขึ้นเพราะผ่านการอ้างอิง
Parijat Kalia

ไม่ทุกอย่างถูกส่งผ่านตามค่าเสมอ มันขึ้นอยู่กับสิ่งที่คุณผ่าน (ค่าหรือการอ้างอิง) ดูนี่สิ
Scott Marcus
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.