JavaScript 是一种按引用传递还是按值传递的语言?

1756

原始类型(例如数字、字符串等)通过值传递,但对象不同,因为它们既可以按值传递(在这种情况下,我们认为保存对象的变量实际上是对该对象的引用),也可以按引用传递(当我们认为变量对对象本身持有该对象时)。

虽然最终并不重要,但我想知道呈现参数传递约定的正确方式是什么。是否有来自JavaScript规范的节选,定义了关于此事应该是什么语义?


8
我认为你不小心颠倒了传值和传引用的定义... "传值(如果我们认为一个变量持有一个对象实际上是指向该对象的引用),传引用(当我们认为变量持有对象本身时)" - Niko Bellic
11
是的。无论在任何编程语言中的函数调用中语法如何,传递引用都表示传递的变量所关联的数据在传递到函数时不会被复制,因此函数对传递的变量所做的任何修改将在函数调用终止后保留在程序中。传值则意味着将变量相关的数据实际上复制到函数中传递,当变量超出函数体作用域并且函数返回时,该函数对变量所做的任何修改都将丢失。 - John Sonderson
9
这个老问题有点棘手,因为它的得票最高的答案是错误的。JavaScript 严格来说是“按值传递”。 - Pointy
9
术语令人遗憾地令人困惑。事实上,“传值”和“传引用”这些术语比许多更现代的编程语言特性还要早。 “值”和“引用”这两个词 具体 指函数调用表达式中的参数。JavaScript始终在调用函数之前计算函数调用参数列表中的每个表达式,因此参数始终是值。令人困惑的是,对象的引用是常见的JavaScript值。然而,这并不意味着它是一种“传引用”的语言。 - Pointy
4
“按引用传递”特指以下情况:如果你有var x=3, y=x; f(x); alert(y === x);,那么函数f()可以使得alert报告false而不是true。在JavaScript中,这是不可能的,所以它不是“按引用传递”。能够传递可修改对象的引用是好的,但这并不是“按引用传递”的意思。就像我说的,术语混淆真是令人遗憾。 - Pointy
显示剩余12条评论
35个回答

1995

在 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将是100obj2.item将读取"changed"。但实际上,num保持为10obj2.item仍然是"unchanged"

事实上,传递的项是按值传递的。但是,按值传递的项本身是一个引用。技术上,这称为共享调用

在实际应用中,这意味着如果更改参数本身(如numobj2),不会影响输入到参数的项。但是,如果更改参数的内部,则会向上传播(如obj1)。


49
这与C#完全相同(或者至少在语义上是)。对象有两种类型:值类型(原始类型)和引用类型。 - Peter Lee
72
我认为这个词也在Java中使用:按值传递引用。 - Jpnh
348
真正的原因是,在changeStuff函数内部,num、obj1和obj2都是引用。当您更改obj1所引用的对象的“item”属性时,实际上是更改了最初设置为“unchanged”的“item”属性的值。当您将obj2赋值为{item: "changed"}时,实际上是更改了对一个新对象的引用(该对象在函数退出时立即超出范围)。如果您将函数参数命名为numf、obj1f和obj2f之类的名称,则更容易看出发生了什么。然后您会发现参数隐藏了外部变量名称。 - jinglesthula
17
@BartoNaz 不完全是这样。你想要的是通过引用传递引用,而不是通过值传递引用。但是JavaScript始终按值传递引用,就像它按值传递其他所有内容一样。(相比之下,C#具有类似于JavaScript和Java的传递引用方式的传递引用-by-value行为,但允许您使用“ref”关键字指定传递引用-by-reference。)通常,您只需使函数返回新对象,并在调用函数时进行赋值。例如,foo = GetNewFoo(); 而不是 GetNewFoo(foo); - Tim Goodman
76
尽管这个答案最受欢迎,但它可能会有些令人困惑,因为它陈述了“如果它是纯值传递”。JavaScript 确实 是纯值传递。但是传递的值是一个引用。这绝不限于参数传递。您可以通过 var obj1 = { item: 'unchanged' }; var obj2 = obj1; obj2.item = 'changed'; 来简单复制变量,并观察到与您的示例中相同的效果。因此,我个人更喜欢Tim Goodman的答案。 - chiccodoro
显示剩余28条评论

632

它始终是按值传递,但对于对象,变量的值是一个引用。因此,当您传递一个对象并更改其成员时,这些更改会在函数外部保持不变。这使得它看起来像是按引用传递。但是,如果您实际上更改了对象变量的值,您将看到更改不会持久,证明它实际上是按值传递。

示例:

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

16
@daylight:实际上,您是错误的;如果通过const引用传递参数,并试图更改对象,则会导致错误而不是仅仅失败。在C++中尝试将新值分配给const引用会被编译器拒绝。以用户角度来看,这就是按值传递和按const引用传递之间的区别。 - deworde
5
@daylight: 这不是常量引用。在 changeObject 中,我已将 x 更改为包含对新对象的引用。x = {member:"bar"}; 相当于 x = new Object(); x.member = "bar"; 顺便说一下,C# 也是如此。 - Tim Goodman
3
对于C#,如果你在函数外部使用了ref关键字,就可以通过引用传递(而非默认的值传递)来传递引用。这样,将其更改为指向new Object()将会持久保留。 - Tim Goodman
16
很难回答“为什么”,但我想指出Java和C#的设计者做出了类似的选择;这不仅仅是JavaScript的怪异之处。实际上,它非常一致地按值传递参数,让人感到困惑的是值可以是引用。这与在C++中按值传递指针并解除引用以设置成员变量没有多大区别。没有人会惊讶于该更改持续存在。但由于这些语言抽象了指针并悄悄地为您执行了解除引用操作,所以人们感到困惑。 - Tim Goodman
73
换句话说,混淆的并不是传值或传引用。总的来说,所有东西都是传值的。混淆的是你不能直接传递一个对象,也不能将对象存储在变量中。每次你认为自己在这样做时,实际上你在传递或存储对该对象的引用。但当你访问其成员时,会发生一种静态解引用的情况,它使得你的变量似乎持有实际对象,而这只是一种虚假的假象。 - Tim Goodman
显示剩余9条评论

170

变量并不“持有”对象,它持有一个引用。可以将该引用分配给另一个变量,现在两者引用同一对象。它总是按值传递(即使该值是引用…)。

如果JavaScript支持按引用传递,则无法修改作为参数传递的变量所持有的值。


6
有点让我困惑。传递引用不就是按引用传递吗? - user3186555
13
作者的意思是:通过传递引用,你传递的是一个引用值(另一种思考方式是传递内存地址的值)。所以如果你重新声明对象,原始对象并不会改变,因为你在不同的内存位置创建了一个新对象。如果你更改属性,原始对象会改变,因为你在原始内存位置上进行了更改(未被重新分配)。 - Huy-Anh Hoang
“按值传递引用”这个说法似乎有些令人困惑和冗余。当传递引用时,当然必须传递一些值。虽然从技术上讲这是正确的,但大多数人默认情况下都会认为除非另有说明,否则任何东西都是按值传递的。所以,当然,引用是按值传递的,除非它本身是按引用传递的(类似于 C 中的指向指针的指针),但在这种情况下,Javascript 甚至不支持这一点,所以我认为这并没有使概念更清晰。 - geg
1
JavaScript 令人困惑的地方在于它没有选择的余地:复杂类型总是间接处理,简单类型总是直接处理。你无法获取整数的引用,也无法阻止传递元组的引用。这有时会让事情变得尴尬。 - Shog9
简单来说,十年后,引用被按值复制。 - Soner from The Ottoman Empire
对于那些对这个答案感到困惑的人:我认为这个答案中的“按引用传递”特指C++风格的按引用传递,比如在C++中的void swapNums(int &x, int &y)或在C#中的void increment(ref int arg) - wlnirvana

150

我的两分钱:这是我理解的方式。(如果我错了,请随意纠正)

现在是时候抛弃你所知道的关于按值/引用传递的一切了。

因为在JavaScript中,无论是按值传递还是按引用传递或其他什么方式都不重要。重要的是对传入函数的参数进行变异还是赋值。

好的,让我尽力解释一下我的意思。假设你有几个对象。

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

我们所做的是“赋值”… 我们将两个空对象分别赋值给变量“object1”和“object2”。
现在,假设我们更喜欢object1… 所以,我们“赋值”一个新的变量。
var favoriteObject = object1;

接下来,出于某种原因,我们决定喜欢第二个对象。因此,我们进行了一些重新分配。
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';

Again, we pick a favorite.

var favoriteString = string1;

我们的favoriteString和string1变量都被赋值为“Hello world”。现在,如果我们想要更改我们的favoriteString,会发生什么?

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

哎呀....发生了什么事。我们无法通过更改favoriteString来更改string1...为什么?因为我们没有“更改”我们的字符串对象。我们所做的只是将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中将参数传递给函数也表示赋值。 它们是相同的,这两个变量的连接方式完全相同(也就是说,除非您计算它们被分配给同一个对象)。
只有在底层对象发生变异时,“修改变量”才会影响不同的变量(在这种情况下,您并没有修改变量,而是修改了对象本身)。
没有区分对象和基元之间的差别,因为它的工作方式与如果您没有一个函数,只使用等号将新变量分配给它一样。
唯一需要注意的是,当您传递给函数的变量名称与函数参数的名称相同时。当这种情况发生时,您必须将函数内部的参数视为函数专用的全新变量(因为它是)。
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'

3
对于任何C程序员而言,想一想char*。foo(char *a){a="hello";}什么也没做,但如果你这样做foo(char *a){a[0]='h';a[1]='i';a[2]=0;},它会在外部发生改变,因为 a是通过值传递的内存位置,它引用了一个字符串(char数组)。在C中,通过值传递结构体(类似于js对象)是被允许的,但不推荐。JavaScript只是强制执行这些最佳实践,并隐藏不必要和通常不需要的冗余代码...这确实使阅读更容易。 - technosaurus
3
这是正确的 - 术语“按值传递”和“按引用传递”在编程语言设计中有特定含义,而这些含义与对象变异毫不相关。它关乎于函数参数如何工作。 - Pointy
2
现在我明白 obj1 = obj2 的意思是 obj1 和 obj2 都指向同一个引用位置,如果我修改 obj2 的内部,引用 obj1 将会暴露相同的内部。那么我该如何复制一个对象,使得当我执行 source = { "id":"1"}; copy = source /*这是错误的*/; copy.id="2" 时,source 仍然是 {"id":"1"} 呢? - Machtyn
1
我发布了另一个答案,希望通过传统定义来减少混淆。在自动解引用之前的内存指针时代,"按值传递"和"按引用传递"的传统定义已经被定义。人们非常清楚,对象变量的值实际上是内存指针位置,而不是对象本身。虽然你对赋值和变异的讨论可能有用,但没有必要放弃传统术语及其定义。变异、赋值、按值传递、按引用传递等概念之间不能相互矛盾。 - C Perkins
“Number” 也是不可变的吗? - ebram khalil
显示剩余4条评论

98

这些短语/概念在JS创建之前就已经定义了,它们并不能准确地描述JavaScript的语义。我认为试图将它们应用于JS会比不使用它们更加混乱。

所以不要过分关注“按引用传递/按值传递”。

考虑以下内容:

  1. 变量是指向值的指针
  2. 重新分配变量仅仅是将该指针指向一个新值。
  3. 重新分配变量永远不会影响其他指向相同对象的变量,因为每个变量都有自己的指针。

因此,如果非要给它命名,我会说“按指针传递”--我们在JS中不处理指针,但底层引擎会。


// 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 这些问题值得在 Stack Overflow 上单独提问,而且可能已经有更好的答案了。话虽如此,1) 我在浏览器开发工具中运行了一个类似于你描述的循环函数的内存分析,并在整个循环过程中看到了内存使用量的波动。这似乎表明在每次迭代中确实创建了新的相同对象。当这些峰值突然下降时,垃圾收集器刚刚清理了一组这些未使用的对象。 - geg
1
@MichaelHoffmann 2) 关于类似 var a = b 的语句,JavaScript 并没有提供使用指针的机制,因此变量永远不能指向指针(就像在 C 中一样),尽管底层的 JavaScript 引擎无疑会使用它们。所以...var a = b 将把 a 指向“右操作数的指针”。 - geg
2
没有必要忘记“按引用/值传递”!这些术语具有历史意义,准确描述了您试图描述的内容。如果我们放弃历史术语和定义,并变得太懒以至于不愿意学习它们最初的含义,那么我们就失去了代际之间有效沟通的能力。没有好的方法来讨论不同语言和系统之间的差异。相反,新程序员需要学习和理解传统术语以及它们的起源和原因。否则,我们就会共同失去知识和理解。 - C Perkins
1
你的图示真的很有帮助 - 谢谢。 - Peter
显示剩余3条评论

28

一个函数之外的对象通过给予该对象的引用被传递到函数中。

当您使用该引用来操作其对象时,外部对象就会受到影响。然而,如果在函数内部决定将引用指向其他内容,则根本不会影响外部对象,因为您所做的只是将引用重新指向其他内容。


27
想象一下,它总是按值传递。然而,对象的值不是对象本身,而是对该对象的引用。
以下是一个例子,传递一个数字(原始类型)。
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}

27
在《JavaScript权威指南》的第11章第2节中详细解释了按值传递和按引用传递的复制、传递和比较。在讨论通过引用操纵对象和数组之前,需要澄清一个术语问题。术语“按引用传递”可能有几种意义。对一些读者而言,该术语是指一种函数调用技术,允许函数向其参数分配新值,并使得这些修改后的值在函数外部可见。但是,在本书中,“按引用传递”的含义与此不同。在这里,我们仅指将对象或数组的引用(而非对象本身)传递给函数。函数可以使用该引用来修改对象的属性或数组的元素。但是,如果函数使用一个指向新对象或数组的引用覆盖原有的引用,这种修改就无法在函数外部显示出来。熟悉该术语其他含义的读者可能更倾向于说对象和数组是按值传递的,但实际上传递的值是一个引用,而非对象本身。

哇,这真是令人困惑。谁会理智地定义一个公认的术语来表示完全相反的意思,然后又以那种方式使用它呢?难怪这个问题上有那么多答案如此混乱。 - Jörg W Mittag
这个回答是理解其他人在这个主题上写作的关键。大多数人并不意识到术语“按引用传递”有两个定义,因此当你阅读他们的解释时,你必须根据上下文推断出他们使用的术语含义。此答案中链接的书籍章节对于更深入地了解这个主题也非常有用。 - Mark

22
JavaScript始终是按值传递的;一切都是值类型。对象是值,对象成员函数本身也是值(请记住,在JavaScript中函数是一级对象)。此外,“JavaScript中一切都是对象”的概念是错误的。字符串、符号、数字、布尔值、null和undefined是原始类型。它们有时可以利用从其基本原型继承的某些成员函数和属性,但这只是为了方便。这并不意味着它们本身就是对象。可参考以下示例:
console.log(typeof "hello world"); // "string"
console.log(typeof 3); // "number"
console.log(typeof true); // "boolean"

x = "test";
console.log(x.foo);
x.foo = 12;
console.log(x.foo);

在两个console.log中,你会发现值为undefined


17
有时候变量不一定总是按值传递。根据 MDC 的说法,“如果你将一个对象(例如数组或用户自定义对象等非原始值)作为参数传递,那么函数中会传递对该对象的引用。” - Nick
50
@Nick:它总是按值传递。 将对象的引用“按值”传递给函数。 这不是按引用传递。 "按引用传递" 几乎可以被认为是传递变量本身,而不是其值;函数对参数所做的任何更改(包括将其替换为完全不同的对象!)都将反映在调用方中。 在JS中最后一点是不可能的,因为JS不是按引用传递,而是按值传递引用。这种区别微妙,但对于理解其限制非常重要。 - cHao
11
MDN是一个用户编辑的维基百科,其中有错误。规范参考是ECMA-262。请查看第8节“引用规范类型”,其中解释了如何解析引用,以及第8.12.5节“[[Put]]”,该节用于将赋值表达式解释为引用,并且在对象强制转换方面还有第9.9节ToObject。对于原始值,Michael已经按照规范解释了ToObject的作用。但是,请参阅第4.3.2节原始值。 - Garrett
2
@WonderLand:不,他不是。那些从未能够通过引用传递的人可能永远无法理解按引用传递和按值传递引用之间的差异。但它们确实存在,并且很重要。我不想因为听起来更容易而误导人们。 - cHao
1
对于像我一样感到困惑的人,https://dev59.com/s3RC5IYBdhLWcg3wOOP1 第二受欢迎的回答进行了详细解释。 - LiweiZ
显示剩余13条评论

20

在JavaScript中,一个值的类型独立地决定了这个值是通过值复制还是通过引用复制来赋值的。

原始值始终是通过值复制进行赋值/传递的

  • null
  • undefined
  • 字符串
  • 数字
  • 布尔值
  • ES6中的符号

复合值始终通过引用复制进行赋值/传递

  • 对象
  • 数组
  • 函数

例如:

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则被分配了该值的另一个副本。当更改b时,您并没有以任何方式更改a中的值。
但是,cd都是指向同一共享值[1,2,3]的单独引用,这是一个复合值。重要的是要注意,cd都不“拥有”[1,2,3]值--它们只是相等的对等引用。因此,当使用任一引用来修改实际共享的array值本身(例如使用.push(4)),它只会影响一个共享值,而两个引用将引用新修改的值[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的指针而不是数组的引用--但是在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]值。
要有效地通过值复制传递复合值(例如数组),您需要手动复制它,以便传递的引用不仍然指向原始值。例如:
foo( a.slice() );

可以通过引用复制传递的复合值(对象、数组等)

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

var obj = {
    a: 2
};

foo( obj );

obj.a; // 42

在这里,obj 充当标量基本属性 a 的包装器。当它传递到 foo(..) 时,会传递 obj 引用的副本并设置为 wrapper 参数。现在我们可以使用 wrapper 引用来访问共享对象并更新其属性。函数完成后,obj.a 将看到更新后的值 42

来源


你首先声明“复合值总是通过引用复制进行分配/传递”,然后你又声明“将引用的副本分配给x”。在你所谓的“复合值”情况下,实际变量值就是引用(即内存指针)。正如你所解释的那样,引用被复制...因此变量的值被复制,再次强调引用就是值。这意味着JavaScript对于所有类型都是按值传递的。按值传递意味着传递变量值的副本。无论该值是对象/数组的引用都没有关系。 - C Perkins
你引入了新的术语(值复制/引用复制),这只会让事情变得更加复杂。实际上只有复制品。如果你传递一个原始类型,你传递的是实际原始数据的副本,如果你传递一个对象,你传递的是对象内存位置的副本。这就是你需要说的全部。任何更多的解释只会让人更加困惑。 - Scott Marcus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接