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


1403

基本类型(数字,字符串等)按值传递,但是对象是未知的,因为它们都可以按值传递(如果我们认为保存对象的变量实际上是对该对象的引用) )和按引用传递(当我们认为对象的变量包含对象本身时)。

尽管最后并没有什么大不了,但我想知道呈现通过惯例的参数的正确方法是什么。是否有JavaScript规范的摘录,该摘录定义了与此相关的语义?


2
我认为您不小心翻转了对按值传递和按引用传递的定义...“按值传递(以防我们认为保存对象的变量实际上是对该对象的引用)并通过引用(当我们认为对象的变量包含对象本身时)”
Niko Bellic 2014年

5
是。无论语法如何,在以任何编程语言进行的任何函数调用中,按引用传递都意味着与传递的变量关联的数据在传递给函数时都不会被复制,因此该函数对传递的变量所做的任何修改都将保留。函数调用终止后,在程序中单击。值传递意味着与变量关联的数据在传递给函数时实际上已被复制,并且当函数返回时变量超出函数主体的范围时,该函数对该变量所做的任何修改都将丢失。
约翰·桑德森

5
这个老问题有些毒,因为它的强烈支持的答案是错误的。JavaScript严格按值传递
尖尖的2015年

6
@DanailNachev该术语令人困惑。问题是,“按值传递”和“按引用传递”是在许多更现代的编程语言功能之前出现的术语。单词“值”和“引用” 专门指代函数调用表达式中出现的参数。JavaScript始终调用函数之前对函数调用参数列表中的每个表达式求值,因此参数始终是值。令人困惑的是,对对象的引用是常见的JavaScript值。但是,这并不能使其成为“通过引用”的语言。
尖尖的2015年

2
@DanailNachev“通过引用传递”特别意味着,如果您具有var x=3, y=x; f(x); alert(y === x);then函数,f()则可以生成警报报告false而不能true。在JavaScript中,这是不可能的,因此它不是按引用传递的。可以将引用传递给可修改的对象是件好事,但这不是“按引用传递”的意思。正如我所说的,术语如此混乱令人遗憾。
Pointy 2015年

Answers:


1596

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对函数的外部产生影响。
  • 如果该论点是适当的参考,那么一切都会改变。num100并且obj2.item会阅读"changed"

相反,情况是传入的项目是按值传递的。但是,按值传递的项目本身就是参考。从技术上讲,这称为共享呼叫

实际上,这意味着如果您更改参数本身(如num和一样obj2),则不会影响输入该参数的项目。但是,如果您更改参数的INTERNALS,则该值将向上传播(与一样obj1)。


32
这与C#完全相同(或至少在语义上)。对象具有两种类型:值(原始类型)和引用。
彼得·李

53
我认为这也在Java中使用:按值引用。
Jpnh 2012年

295
真正的原因是在changeStuff中,num,obj1和obj2是引用。更改itemobj1引用的对象的属性时,将更改最初设置为“未更改”的item属性的值。为obj2分配值{item:“ changed”}时,您将更改对新对象的引用(当函数退出时,该对象将立即超出范围)。如果将函数命名为numf,obj1f和obj2f之类的函数,就会更清楚地看到发生了什么。然后您会看到参数隐藏了外部变量名称。
jinglesthula 2012年

13
@BartoNaz不是。您想要的是按引用传递引用,而不是按值传递引用。但是JavaScript总是按值传递引用,就像它按值传递其他所有内容一样。(为了进行比较,C#具有类似于JavaScript和Java的按值传递引用的行为,但是允许您使用ref关键字指定按引用传递引用。)通常,您只需让函数返回新对象,然后执行调用函数时的赋值。例如,foo = GetNewFoo();代替GetNewFoo(foo);
Tim Goodman 2013年

55
尽管此答案是最受欢迎的答案,但可能会引起混淆,因为它指出“如果纯粹是按价值传递”。JavaScript 纯按值传递。但是传递的值是参考。这根本不限于参数传递。您可以简单地通过复制变量,var obj1 = { item: 'unchanged' }; var obj2 = obj1; obj2.item = 'changed';并观察与示例相同的效果。因此,我个人将蒂姆·古德曼的答案
chiccodoro 2014年

475

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

例:

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 ref传递,则尝试执行changeObject会导致错误,而不仅仅是失败。尝试在C ++中为const引用分配一个新值,编译器会拒绝它。用用户术语来说,这就是按值传递和按常量引用传递之间的区别。
deworde 2011年

5
@daylight:这不是常量引用。在中changeObject,我已更改x为包含对新对象的引用。 顺便说一句x = {member:"bar"};x = new Object(); x.member = "bar";这相当于我所说的C#也是如此。
蒂姆·古德曼

2
@daylight:对于C#,您可以从函数外部看到它,如果使用ref关键字,则可以按引用传递引用(而不是按值传递引用的默认值),然后指向a的更改new Object() 继续存在。
蒂姆·古德曼

11
@adityamenon很难回答“为什么”,但是我要指出Java和C#的设计者做出了类似的选择。这不仅仅是一些JavaScript怪异。确实,它是始终如一的价值传递,让人感到困惑的是,价值可以作为参考。它与在C ++中传递指针(按值)然后取消引用以设置成员没有什么不同。这种变化持续存在,没有人会感到惊讶。但是由于这些语言抽象了指针并默默地为您取消引用,所以人们会感到困惑。
Tim Goodman 2013年

41
换句话说,这里令人困惑的事情不是按值传递/按引用传递。一切都是按价值传递,句号。令人困惑的是,您不能传递对象,也不能将对象存储在变量中。每当您认为这样做时,实际上就是传递或存储对该对象的引用。但是,当您访问它的成员时,会发生无声取消引用,这使您的变量永久保存了实际对象这一虚构想法永久存在。
Tim Goodman 2013年

150

变量不会“保留”对象;它具有参考。您可以将该引用分配给另一个变量,现在两者都引用同一个对象。它总是按值传递(即使该值是引用...)。

无法更改作为参数传递的变量所持有的值,如果JavaScript支持通过引用传递,则可以实现。


2
这让我有些困惑。不通过参考传递参考吗?

8
作者的意思是通过传递引用,您传递的是参考值(另一种认为是传递内存地址值的方法)。因此,如果您重新声明该对象,则原始对象不会更改,因为要在其他内存位置创建一个新对象。如果更改属性,则原始对象也会发生更改,因为您是在原始内存位置(未重新分配)更改了它。
惠安·昂

113

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

现在该丢掉您所知道的关于按值/引用传递的所有信息。

因为在JavaScript中,按值,按引用或其他方式传递都无关紧要。重要的是传递给函数的参数的变异与赋值。

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

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

我们要做的是“赋值” ...我们已经为变量“ object1”和“ object2”分配了2个单独的空对象。

现在,让我们说我们更喜欢object1 ...因此,我们“分配”了一个新变量。

var favoriteObject = object1;

接下来,无论出于何种原因,我们决定我们更喜欢对象2。因此,我们只需进行一些重新分配。

favoriteObject = object2;

对象1或对象2均未发生任何事情。我们根本没有更改任何数据。我们所做的只是重新分配了我们最喜欢的对象。重要的是要知道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;

我们的收藏夹字符串和字符串1变量都分配给了“ Hello world”。现在,如果我们想更改我们的收藏夹字符串?会发生什么???

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

呃哦...发生了什么事。我们无法通过更改favoriteString来更改string1 ...为什么?因为我们没有更改字符串对象。我们所做的只是将“ favoriteString” 变量 “重新分配” 为新字符串。这实际上将其与string1断开了连接。在上一个示例中,当我们重命名对象时,我们没有分配任何东西。(但是,不是将变量本身分配给...,但是,我们确实将name属性分配给了新的字符串。)相反,我们仅对对象进行了突变,以保持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个变量以完全相同的方式连接(也就是说,它们不是一样,除非您算出它们已分配给同一对象)。

“修改变量”唯一会影响其他变量的时间是基础对象发生突变时(在这种情况下,您尚未修改变量,而是对象本身。

在对象和基元之间进行区分是没有意义的,因为它的工作方式与您没有函数并且只是使用等号分配给新变量的方式相同。

唯一的难题是,当您传递给函数的变量名称与函数参数的名称相同时。发生这种情况时,您必须将函数内部的参数视为是函数专有的全新变量(因为它是)

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通过值传递的存储位置引用了字符串(char数组)。允许通过C中的值传递结构(类似于js对象),但不建议这样做。JavaScript只是简单地实施了这些最佳实践,并隐藏了不必要的(通常是不希望的)废纸...……这肯定使阅读更容易。

2
这是正确的-条款 传递的价值传递通过引用在编程语言设计的意义,这些意义什么都没有做对象突变。所有关于函数参数的工作方式。
尖尖的2015年

2
现在,我了解到obj1 = obj2意味着obj1和obj2现在都指向相同的引用位置,并且如果我修改obj2的内部结构,则引用obj1将公开相同的内部结构。我该如何复制对象source = { "id":"1"}; copy = source /*this is wrong*/; copy.id="2",源仍然是{“ id”:“ 1”}?
Machtyn

1
我用传统定义发布了另一个答案,以期减少混乱。传统的“按值传递”和“按引用传递”定义是在自动取消引用之前的内存指针中定义的。完全理解的是,对象变量的值实际上是内存指针位置,而不是对象。尽管您对赋值与变异的讨论可能很有用,但没有必要抛弃传统术语或它们的定义。变异,赋值,按值传递,按引用传递等不得相互矛盾。
C Perkins

难道“数字”也是“不变的”吗?
ebram khalil

72

考虑以下:

  1. 变量是指向内存中值的指针
  2. 重新分配变量只是将该指针指向一个新值。
  3. 重新分配变量将永远不会影响指向同一对象的其他变量

因此,不要忘记“按引用/值传递”,不要因为“按引用/值传递而挂断电话,因为:

  1. 这些术语仅用于描述语言的行为,而不一定是实际的基础实现。由于这种抽象,丢失了对体面的解释必不可少的关键细节,这不可避免地导致当前情况,即单个术语不能充分描述实际行为,因此必须提供补充信息
  2. 这些概念最初并不是为了描述javascript而定义的,因此当它们只会增加混乱时,我不觉得必须使用它们。

回答您的问题:传递指针。


// 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这些应该解决自己的问题,而且可能已经得到了我无法解决的更好的答案。话虽如此,1)我在浏览器开发工具中为一个循环功能(例如您所描述的功能)运行了一个内存配置文件,并在整个循环过程中看到了内存使用量的峰值。这似乎表明在循环的每次迭代中确实在创建新的相同对象。当尖峰突然掉下时,垃圾收集器只是清理了一组这些未使用的对象。
geg

1
@MichaelHoffmann 2)关于var a = bjavascript,它没有提供使用指针的机制,因此,尽管底层的javascript引擎无疑会使用它们,但变量永远不能指向指针(就像在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örgW Mittag

16

JavaScript总是按值传递;一切都是价值类型。

对象是值,对象的成员函数本身就是值(请记住,函数是JavaScript中的一流对象)。另外,关于JavaScript中的所有内容都是对象的概念;这是错误的。字符串,符号,数字,布尔值,空值和未定义是原语

有时他们可以利用从其基本原型继承的一些成员函数和属性,但这只是为了方便。这并不意味着它们本身就是对象。请尝试以下操作以供参考:

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

在这两个警报中,您都将找到未定义的值。


12
-1,它并不总是按值传递。来自MDC:“如果将对象(即非原始值,例如Array或用户定义的对象)作为参数传递,则对该对象的引用将传递给函数。”
尼克,2010年

37
@Nick:它总是按值传递。期。对对象的引用按值传递给函数。这不是通过引用。“按引用传递”几乎可以被认为是传递变量本身,而不是传递其值。函数对参数所做的任何更改(包括将其完全替换为其他对象!)都将反映在调用方中。最后一点在JS中是不可能的,因为JS不会按引用传递-而是按值传递引用。这种区别是微妙的,但对于理解其局限性却很重要。
cHao 2012年

1
对于将来的堆栈器...关于您的引用:x = "teste"; x.foo = 12;等等。仅仅因为属性不是持久性的,并不意味着它不是对象。正如MDN所说:在JavaScript中,几乎所有东西都是对象。除null和undefined以外的所有基本类型均被视为对象。可以为它们分配属性(某些类型的分配属性不是持久性的),并且它们具有对象的所有特征。 链接
slacktracer 2012年

9
MDN是用户编辑的Wiki,在那是错误的。规范性参考文献为ECMA-262。参见S.8“参考规范类型”,它解释了如何解析参考,以及8.12.5“ [[Put]]”,它用于解释对参考的AssignmentExpression,以及对象强制9.9 ToObject。对于原始值,Michael已经按照规范说明了ToObject的功能。但另见。4.3.2原始值。
加勒特

1
@WonderLand:不,他不是。从未能够通过引用传递的人可能永远无法理解按引用传递和按值传递引用之间的区别。但是他们在那里,而且很重要。我不在乎误导人们只是因为听起来听起来容易。
cHao

12

在JavaScript中,值的类型控制该值是由value-copy还是由reference-copy分配。

原始值始终由value-copy分配/传递

  • 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

但是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引用的副本xx并且a是指向相同[1,2,3]值的单独引用。现在,在函数内部,我们可以使用该引用来改变值本身(push(4))。但是,当我们进行赋值时x = [4,5,6],这绝不会影响初始引用a指向的位置-仍然指向(现在已修改)[1,2,3,4]值。

array通过value-copy 有效地传递复合值(如),您需要手动制作它的副本,以使传递的引用仍然不指向原始值。例如:

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

9

嗯,这是关于“性能”和“速度”以及编程语言中简单的“内存管理”一词。

在JavaScript中我们可以把值在2层:TYPE1 - objects类型2 -所有其他类型的值,如stringboolean&等

如果您将内存想象成下面的正方形,那么在每个正方形中只能保存一个type2值:

在此处输入图片说明

每个type2值(绿色)是一个正方形,而type1值(蓝色)是其中的一

在此处输入图片说明

关键是,如果要指示类型2值,则地址是简单的,但如果要对类型1值执行相同的操作,那根本就不容易!:

在此处输入图片说明

还有一个更复杂的故事:

在此处输入图片说明

因此,这里的引用可以挽救我们: 在此处输入图片说明

虽然此处的绿色箭头是一个典型变量,而紫色箭头是一个对象变量,所以因为绿色箭头(典型变量)只有一项任务(这表示一个典型值),所以我们不需要将其值与因此,我们将绿色箭头及其值移动到所有位置,所有分配,函数等中……

但是我们不能用紫色箭头做同样的事情,我们可能想将“ john”单元格移到这里或其他许多事情...,所以紫色箭头会固定在其位置,只有分配给它的典型箭头会移动...

一个非常令人困惑的情况是您无法意识到所引用变量的变化,让我们来看一个非常好的示例:

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中的所有原始类型(字符串,数字,布尔值,未定义和null)。
  • 为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" }
  • JavaScript引擎将对象分配给变量 c,并且它指向一些内存,例如(0x012)。
  • 当d = c时,在此步骤中 d指向相同的位置(0x012)。
  • 更改任何变量的值都会更改两个变量的值。
  • 功能就是对象

特殊情况,通过引用传递(对象)

c = {"name" : "jane"};
console.log(c); // { "name" : "jane" }
console.log(d); // { "name" : "doe" }
  • equal(=)运算符设置新的内存空间或地址

在您所谓的特殊情况下,不是分配运算符导致了内存空间的分配,而是对象文字本身。Curley括号表示法将导致创建新对象。该属性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

哈哈,我尝试过...我建议的编辑更改太多,不允许amd
amaster

4

语义学!设置具体的定义必然会导致某些答案和注释不兼容,因为即使使用相同的单词和短语它们也不会描述相同的事物,但是克服这种混乱至关重要(尤其是对于新程序员而言)。

首先,并不是每个人都似乎掌握了多个抽象级别。在第4代或第5代语言上学习过的较新的程序员可能难以将注意力集中在汇编或C程序员熟悉的概念上,而这些概念不是由指向指针的指针阶段化的。引用传递不仅仅意味着具有使用功能参数变量更改引用对象的能力。

变量:符号的组合概念,它引用内存中特定位置的值。这个术语通常过于繁琐,无法在讨论细节时单独使用。

符号:用于引用变量的文本字符串(即变量的名称)。

:存储在存储器中并使用变量符号引用的特定位。

内存位置:存储变量值的位置。(该位置本身由与该位置存储的值分开的数字表示。)

函数参数:在函数定义中声明的变量,用于引用传递给函数的变量。

函数参数:函数外部的变量,调用者将其传递给函数。

对象变量:其基本基础值不是“对象”本身的变量,而是其值是指向内存中存储对象实际数据的另一个位置的指针(内存位置值)。在大多数高级语言中,“指针”方面在各种情况下都可以通过自动取消引用来有效隐藏。

原始变量:其值为实际值的变量。即使自动装箱和各种语言的类对象上下文也可能使此概念变得复杂,但是一般的想法是,变量的值是由变量的符号表示的实际值,而不是指向另一个内存位置的指针。

函数参数和参数不是同一件事。同样,变量的值也不是变量的对象(正如各种人已经指出的那样,但是显然被忽略了)。这些区别对于正确理解至关重要。

按值传递或按共享共享(用于对象):函数参数的值被复制到另一个存储位置,该位置由函数的参数符号引用(无论它在堆栈还是堆上)。换句话说,函数参数接收到传递的参数值的副本...并且(关键)参数的值不会被调用函数更新/更改/更改。请记住,对象变量的值不是对象本身,而是对象的指针,因此按值传递对象变量会将指针复制到函数参数变量。函数参数的值指向内存中完全相同的对象。可以通过函数参数直接更改对象数据本身,但是函数参数的值是“永不更新”,因此它将继续指向相同的值整个对象,甚至在函数调用之后(即使其对象的数据已更改,或者功能参数完全分配了另一个对象)也是如此。仅仅因为所引用的对象可通过功能参数变量进行更新而得出结论,认为函数参数是通过引用传递的,这是不正确的。

呼叫/转接参考:可以/将通过相应的函数参数直接更新函数参数的值。如果有帮助,函数参数将成为该参数的有效“别名”-它们有效地引用了相同内存位置处的相同值。如果函数参数是对象变量,则更改对象数据的能力与传递值的情况没有区别,因为函数参数仍将指向与参数相同的对象。但是在对象变量的情况下,如果将功能参数设置为完全不同的对象,则参数同样会指向不同的对象,这在传递值的情况下不会发生。

JavaScript不通过引用传递。如果仔细阅读,您会发现所有相反的意见都会误解传递值的含义,并且他们错误地得出结论,通过功能参数更新对象数据的能力与“传递值”同义。

对象克隆/复制:创建一个新对象,并复制原始对象的数据。这可以是深层副本,也可以是浅层副本,但重点是要创建一个新对象。创建对象的副本是与值传递不同的概念。一些语言区分类对象和结构(或类似结构),并且对于传递不同类型的变量可能具有不同的行为。但是,在传递对象变量时,JavaScript不会自动执行此类操作。但是缺少自动对象克隆功能并不能转换为按引用传递。


4

JavaScript按值传递原始类型,按引用传递对象类型

现在,人们喜欢无休止地争论“按引用传递”是否是描述Java等人的正确方法。确实可以。关键是这样的:

  1. 传递对象不会复制该对象。
  2. 传递给函数的对象可以通过函数修改其成员。
  3. 传递给函数的原始值不能被该函数修改。复制。

在我的书中称为通过引用传递。

- 布莱恩碧 - 哪些编程语言是按引用传递?


更新资料

这是对此的反驳:

JavaScript中没有可用的“通过引用传递”。


@Amy因为这是按值传递,而不是按引用传递。这个答案很好地表明了差异:stackoverflow.com/a/3638034/3307720
nasch

@nasch我明白区别。#1和#2描述了按引用传递语义。#3描述按值传递语义。
艾米(Amy)

@Amy 1、2和3都与按值传递一致。要通过引用传递,您还需要4:将引用分配给函数内部的新值(使用=运算符),还需要在函数外部重新分配引用。Java并非如此,这使得它只能按值传递。传递对象时,将指针传递给该对象,然后按值传递该指针。
nasch

通常,这不是“按引用传递”的意思。您已满足我的询问,但我不同意您的意见。谢谢。
艾米(Amy)

“在我的书中,这称为参考传递。” –在曾经编写的每本编译器书,解释器书,编程语言理论书和计算机科学书中,事实并非如此。
JörgW Mittag

3

我了解这一点的简单方法...

  • 调用函数时,您传递的是参数变量的内容(引用或值),而不是变量本身。

    var var1 = 13;
    var var2 = { prop: 2 };
    
    //13 and var2's content (reference) are being passed here
    foo(var1, var2); 
  • 在函数内部,参数变量inVar1inVar2接收要传递的内容。

    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 2015年

@DanailNachev:尽管从技术上讲可能是正确的,但差异仅对ECMAScript原语不是的可变对象可见。
JörgW Mittag

3

对于编程语言律师,我阅读了ECMAScript 5.1的以下部分(比最新版本更容易阅读),并在ECMAScript邮件列表中进行了询问

TL; DR:一切都是通过值传递的,但是对象的属性是引用,并且标准中非常缺乏对象的定义。

构造论证清单

第11.2.4节“自变量列表”在生成仅包含1个自变量的自变量列表时说以下内容:

生产ArgumentList:AssignmentExpression的评估如下:

  1. 令ref为评估AssignmentExpression的结果。
  2. 令arg为GetValue(ref)。
  3. 返回唯一项为arg的列表。

本节还列举了参数列表包含0或> 1个参数的情况。

因此,所有内容都通过引用传递。

访问对象属性

第11.2.1节“属性访问器”

生产的MemberExpression:MemberExpression [Expression]的计算如下:

  1. 令baseReference为评估MemberExpression的结果。
  2. 令baseValue为GetValue(baseReference)。
  3. 令propertyNameReference为计算Expression的结果。
  4. 设propertyNameValue为GetValue(propertyNameReference)。
  5. 调用CheckObjectCoercible(baseValue)。
  6. 设propertyNameString为ToString(propertyNameValue)。
  7. 如果要评估的语法产生包含在严格模式代码中,则使strict为真,否则使strict为假。
  8. 返回类型为Reference值,其基值为baseValue,其引用名称为propertyNameString,其严格模式标志为strict。

因此,对象的属性始终可以用作参考。

参考上

在第8.7节“引用规范类型”中对此进行了描述,引用不是该语言中的实类型-它们仅用于描述delete,typeof和赋值运算符的行为。

“对象”的定义

在5.1版中定义“对象是属性的集合”。因此,我们可以推断出,对象的值就是集合,但是关于什么是集合的值在规范中定义得很差,需要一些努力才能理解。


永远不会令我惊讶的是,有多少人对值传递的参数,引用传递的参数,对整个对象的操作以及对其属性的操作之间的区别感到困惑。1979年,我没有获得计算机科学学位,而是选择在我的MBA课程中增加约15个小时的CS选修课。尽管如此,我很快就意识到,我对这些概念的掌握程度至少与拥有计算机科学或数学学位的任何同事所掌握的一样好。学习汇编器,它将变得很清楚。
戴维·格雷

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。没有理由引入新术语。
桑耶夫

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
“按副本传递” ===按值传递
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 ++的&运算符或C#的“ ref”关键字,两者都是通过引用传递的。
马特·格里尔

更容易的是,一切都通过JavaScript的值传递。
Scott Marcus '18


-3
  1. 像字符串,数字之类的原始类型变量始终按值传递。
  2. 基于这两个条件,数组和对象按引用传递或按值传递。

    • 如果要使用新的对象或数组更改该对象或数组的值,则按值传递。

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

    在这里,您将新对象或数组分配给旧对象。您没有更改旧对象的属性值,因此按值传递。

    • 如果要更改对象或数组的属性值,则通过引用传递它。

      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
不要将赋值运算符与函数调用混淆。当您将新数据分配给现有变量时,旧数据的引用计数会减少,并且新数据将与旧变量关联。基本上,变量最终指向新数据。属性变量也是如此。由于这些分配不是函数调用,因此它们与值传递或引用传递无关。
约翰·桑德森

1
不,一切总是通过价值传递。它仅取决于您要传递的内容(值或引用)。看到这个
Scott Marcus

-3
  1. 基元(数字,布尔值等)按值传递。
    • 字符串是不可变的,因此对它们而言并不重要。
  2. 对象通过引用传递(引用通过值传递)。

不,一切总是通过价值传递。它仅取决于您要传递的内容(值或引用)。看到这个
Scott Marcus '18

您的第二个陈述自相矛盾。
JörgW 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 2015年

不,一切总是通过价值传递。它仅取决于您要传递的内容(值或引用)。看到这个
Scott Marcus '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.