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

Is JavaScript a pass-by-reference or pass-by-value language?

提问人:Danail Nachev 提问时间:2/6/2009 最后编辑:Dave MackeyDanail Nachev 更新时间:9/10/2023 访问量:547509

问:

基元类型(数字、字符串等)是按值传递的,但对象是未知的,因为它们既可以按值传递(在这种情况下,我们认为保存对象的变量实际上是对对象的引用)也可以是按引用传递的(当我们认为对象的变量包含对象本身时)。

虽然最后并不重要,但我想知道什么是通过约定来呈现参数的正确方式。是否有 JavaScript 规范的摘录,它定义了与此相关的语义应该是什么?

JavaScript 按引用传递

评论

8赞 Niko Bellic 8/25/2014
我想你不小心颠倒了你对传递值和引用传递的定义......“传递值(如果我们认为包含对象的变量实际上是对对象的引用)和按引用传递(当我们认为对象的变量包含对象本身时)”
11赞 John Sonderson 10/29/2014
是的。无论语法如何,在任何编程语言的任何函数调用中,按引用传递意味着在传递给函数时不会复制与传递的变量关联的数据,因此函数对传递的变量所做的任何修改都将在函数调用终止后保留在程序中。按值传递意味着与变量关联的数据在传递给函数时实际上被复制,当函数返回时,当变量超出函数主体的范围时,该函数对此类变量所做的任何修改都将丢失。
9赞 Pointy 11/28/2015
这个老问题有点有毒,因为它的重度赞成答案是不正确的。JavaScript 是严格按值传递的。
9赞 Pointy 12/16/2015
@DanailNachev 令人遗憾的是,术语令人困惑。问题是,“按值传递”和“按引用传递”是早于许多更现代的编程语言功能的术语。单词“value”和“reference”特指函数调用表达式中出现的参数。JavaScript 总是在调用函数之前计算函数调用参数列表中的每个表达式,因此参数始终是值。令人困惑的是,对对象的引用是常见的 JavaScript 值。然而,这并不能使它成为一种“通过引用传递”的语言。
4赞 Pointy 12/16/2015
@DanailNachev“通过引用”具体是指,如果您有,则函数可以进行警报报告,而不是。在 JavaScript 中,这是不可能的,所以它不是按引用传递的。可以传递对可修改对象的引用是件好事,但这不是“通过引用传递”的意思。正如我所说,术语如此混乱是一种耻辱。var x=3, y=x; f(x); alert(y === x);f()falsetrue

答:

170赞 Shog9 2/6/2009 #1

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

无法更改作为参数传递的变量所持有的值,如果 JavaScript 支持通过引用传递,这将是可能的。

评论

6赞 10/2/2017
这让我有点困惑。传递引用不是逐个引用吗?
13赞 Huy-Anh Hoang 10/11/2017
作者的意思是,通过传递引用,您正在传递引用值(另一种思考方式是传递内存地址的值)。因此,这就是为什么如果重新声明对象,原始对象不会更改,因为您正在不同的内存位置创建新对象。如果更改属性,则原始对象会更改,因为您在原始内存位置(未重新分配)更改了该属性。
0赞 geg 7/2/2020
“按值传递引用”这句话似乎不必要地令人困惑和多余。当然,在传递引用时,必须传递一些值。虽然从技术上讲是正确的,但除非另有说明,否则大多数人的默认假设可能是按值传递的。因此,引用当然是按值传递的,除非它本身是通过引用传递的(有点像 C 中指向指针的指针),但在这种情况下,Javascript 甚至不支持它,所以我认为它无助于使概念更清晰
1赞 Shog9 7/2/2020
与 JavaScript 的混淆点在于,它没有提供任何选择,@geg:复杂类型总是间接处理,简单类型总是直接处理。无法获取对整数的引用,也无法阻止将引用传递到元组。这。。。只是有时会很尴尬。
0赞 Soner from The Ottoman Empire 8/24/2020
简单地说,十年后,参考是按价值复制的。
22赞 Michael Roberts 2/6/2009 #2

JavaScript 始终是按值传递的;一切都是价值类型的。

对象是值,对象的成员函数本身就是值(请记住,函数是 JavaScript 中的第一类对象)。此外,关于 JavaScript 中的所有内容都是对象的概念;这是错误的。字符串、符号、数字、布尔值、null 值和未定义是基元

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

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

在两者中,您都会发现值为 。console.logundefined

评论

17赞 Nick 10/14/2010
-1,它并不总是按值传递。来自 MDC:“如果将对象(即非基元值,例如 Array 或用户定义的对象)作为参数传递,则对该对象的引用将传递给函数。
50赞 cHao 5/10/2012
@Nick:它总是按值传递。时期。对对象的引用按值传递给函数。这不是通过引用传递。“通过引用传递”几乎可以被认为是传递变量本身,而不是它的值;函数对参数所做的任何更改(包括将其完全替换为不同的对象!)都将反映在调用方中。最后一位在 JS 中是不可能的,因为 JS 不是通过引用传递的——它通过值传递引用。这种区别是微妙的,但对于理解其局限性相当重要。
1赞 slacktracer 8/16/2012
对于未来的堆垛机...关于你的这个参考:等等。仅仅因为属性不是持久的,并不意味着它不是对象。正如 MDN 所说:在 JavaScript 中,几乎所有东西都是一个对象。除 null 和 undefined 之外的所有基元类型都被视为对象。可以为它们赋值属性(某些类型的赋值属性不是持久的),并且它们具有对象的所有特征。 链接x = "teste"; x.foo = 12;
11赞 Garrett 12/15/2013
MDN 是一个用户编辑的 wiki,它在那里是错误的。规范性参考是ECMA-262。参见 S. 8 “The Reference Specification Type”,它解释了如何解析引用,参见 8.12.5 “[[Put]]”,它用于向 Reference 解释 AssignmentExpression,以及 9.9 ToObject。对于基元值,Michael 已经解释了 ToObject 的作用,如规范中所示。但另见第 4.3.2 条原始值。
2赞 cHao 5/6/2016
@WonderLand:不,他不是。从未能够通过引用传递的人可能永远无法理解通过引用传递和通过值传递引用之间的区别。但它们就在那里,而且很重要。我不在乎误导人们,只是因为它听起来更容易。
-3赞 Ross 2/6/2009 #3

确定某些内容是否为“通过引用传递”的一种简单方法是是否可以编写“交换”函数。例如,在 C 语言中,您可以执行以下操作:

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

如果你不能在 JavaScript 中做与此等效的事情,它就不是“通过引用传递”。

评论

22赞 Matt Greer 4/27/2010
这并不是真正的引用传递。您正在将指针传递到函数中,并且这些指针是按值传入的。一个更好的例子是C++的&运算符或C#的“ref”关键字,两者都是真正的引用传递。
0赞 Scott Marcus 4/6/2018
更简单的是,在 JavaScript 中,一切都是通过值传递的。
0赞 Scott Marcus 4/6/2018
stackoverflow.com/questions/42045586/......
1999赞 22 revs, 16 users 42%deworde #4

这在 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
  • 如果根本不是引用,则更改不会对函数的外部产生影响。obj1obj1.itemobj1
  • 如果这个论点是一个适当的参考,那么一切都会改变。 将是 ,并且会读作 。相反,留下来并保持”。num100obj2.item"changed"num10obj2.item"unchanged

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

实际上,这意味着如果更改参数本身(如 和 ),则不会影响输入到参数中的项。但是,如果您更改参数的内部结构,它将传播回去(如)。numobj2obj1

评论

49赞 Peter Lee 12/24/2011
这与 C# 完全相同(或至少在语义上)。对象有两种类型:Value(基元类型)和 Reference。
72赞 Jpnh 1/4/2012
我认为这在 Java 中也使用:按值引用。
348赞 jinglesthula 1/31/2012
真正的原因是在 changeStuff 中,num、obj1 和 obj2 是引用。当您更改 obj1 引用的对象的属性时,您将更改最初设置为“unchanged”的 item 属性的值。当您为 obj2 分配值 {item: “changed”} 时,您正在更改对新对象的引用(当函数退出时,该对象会立即超出范围)。如果您将函数参数命名为 numf、obj1f 和 obj2f 之类的东西,会发生什么会变得更加明显。然后你会看到参数隐藏了外部变量名称。item
17赞 Tim Goodman 8/5/2013
@BartoNaz 不完全是。您想要的是逐个引用传递引用,而不是按值传递引用。但是 JavaScript 总是按值传递引用,就像它按值传递其他所有内容一样。(相比之下,C# 具有类似于 JavaScript 和 Java 的 pass-reference-by-value 行为,但允许您使用关键字指定 pass-reference-by-reference。通常,您只需让函数返回新对象,并在调用函数时执行赋值。例如,代替reffoo = GetNewFoo();GetNewFoo(foo);
76赞 chiccodoro 5/2/2014
虽然这个答案是最流行的,但它可能有点令人困惑,因为它指出“如果它是纯粹的传递值”。JavaScript 纯粹的按值传递。但传递的值是引用。这完全不局限于参数传递。您可以简单地复制变量,并观察到与示例中相同的效果。因此,我个人参考了蒂姆·古德曼(Tim Goodman)的答案var obj1 = { item: 'unchanged' }; var obj2 = obj1; obj2.item = 'changed';
633赞 Tim Goodman 3/16/2011 #5

它始终按值传递,但对于对象,变量的值是引用。因此,当您传递对象并更改其成员时,这些更改将保留在函数之外。这使它看起来像通过引用传递。但是,如果你真的更改了对象变量的值,你会看到更改不会持续存在,证明它确实是按值传递的。

例:

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赞 deworde 7/18/2011
@daylight:其实,你错了;如果它是由 const ref 传递的,尝试执行 changeObject 将导致错误,而不仅仅是失败。尝试在 C++ 中为 const 引用分配一个新值,编译器拒绝它。用用户术语来说,这就是 pass by value 和 pass by const reference 之间的区别。
5赞 Tim Goodman 7/20/2011
@daylight:这不是常量参考。在 中,我已更改为包含对新对象的引用。 等价于我所说的 顺便说一句,C# 也是如此。changeObjectxx = {member:"bar"};x = new Object(); x.member = "bar";
3赞 Tim Goodman 7/20/2011
@daylight:对于 C#,你可以从函数外部看到这一点,如果你使用关键字,你可以通过引用传递引用(而不是默认的按值传递引用),然后对指向 a 的更改保持不变。refnew Object()
16赞 Tim Goodman 7/28/2013
@adityamenon 很难回答“为什么”,但我会注意到 Java 和 C# 的设计者做出了类似的选择;这不仅仅是一些 JavaScript 的怪异之处。真的,它非常一致地传递价值,让人们感到困惑的是,一个价值可以作为参考。这与在 C++ 中传递指针(按值)然后取消引用它以设置成员没有太大区别。没有人会对这种变化持续下去感到惊讶。但是,由于这些语言抽象出指针并默默地为您执行取消引用,因此人们会感到困惑。
74赞 Tim Goodman 7/28/2013
换句话说,这里令人困惑的不是按值传递/按引用传递。一切都是按价值传递的,句号。令人困惑的是,您不能传递对象,也不能将对象存储在变量中。每当你认为你正在这样做时,你实际上是在传递或存储对该对象的引用。但是,当你去访问它的成员时,会发生一种无声的取消引用,这延续了你的变量包含实际对象的虚构。
27赞 igor 5/13/2011 #6

关于按值和引用进行复制、传递和比较的非常详细的解释在“JavaScript:权威指南”一书的这一章中。

在我们离开这个话题之前 通过以下方式操作对象和数组 参考,我们需要澄清一点 的命名法。

短语“路过 reference“可以有多种含义。 对于一些读者来说,这句话指的是 一种函数调用技术,它 允许函数分配新值 到它的论点,并拥有那些 修改后的值在 功能。这不是术语的方式 在本书中使用。

在这里,我们的意思是 简单地说,就是对对象的引用 或数组 -- 不是对象本身 -- 被传递给函数。一个函数 可以使用引用进行修改 对象或元素的属性 数组。但是如果函数 用 对新对象或数组的引用, 该修改是不可见的 在函数之外。

读者 熟悉 的其它含义 这个术语可能更愿意说 对象和数组被传递 value,但传递的值是 实际上是一个参考,而不是 对象本身。

评论

0赞 Jörg W Mittag 6/1/2020
哇,这真是令人难以置信的混乱。谁会把他们一个公认的术语定义为完全相反的意思,然后以这种方式使用它?难怪这里这么多关于这个问题的答案如此混乱。
0赞 Mark 2/19/2021
这个答案是理解其他人在这个主题上写什么的关键。大多数人没有意识到“通过引用传递”一词有两个定义,因此当您阅读他们的解释时,您必须对他们使用的术语的含义做出有根据的猜测。本答案中链接的书籍章节也有助于更深入地理解该主题。
28赞 user779764 6/3/2011 #7

函数外部的对象通过提供对外部对象的引用来传递到函数中。

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

-1赞 Jack 5/5/2012 #8

我发现 Underscore.js 库extend 方法非常有用,当我想将对象作为参数传入时,该参数可以被修改或完全替换。

function replaceOrModify(aObj) {
  if (modify) {

    aObj.setNewValue('foo');

  } else {

   var newObj = new MyObject();
   // _.extend(destination, *sources) 
   _.extend(newObj, aObj);
  }
}
1赞 lid 5/17/2014 #9

我发现的最简洁的解释是在 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

也就是说,实际上基元类型是通过值传递的,而复杂类型是通过引用传递的。

评论

0赞 Scott Marcus 4/6/2018
不,一切都是按值传递的。它只取决于你传递的内容(值或引用)。看这个
150赞 Ray Perea 8/4/2014 #10

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

是时候抛弃你所知道的关于按值/引用传递的所有信息了。

因为在 JavaScript 中,它是按值传递还是按引用或其他方式传递并不重要。 重要的是传递给函数的参数的突变与分配。

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

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

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

现在,假设我们更喜欢 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 怎么办???会发生什么???

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'

评论

3赞 technosaurus 3/26/2015
对于任何 C 程序员来说,想想 char*。 不执行任何操作,但如果执行此操作,则会在外部更改,因为该内存位置是由引用字符串(char 数组)的值传递的。允许在 C 语言中按值传递结构体(类似于 js 对象),但不建议这样做。JavaScript 只是强制执行这些最佳实践,并隐藏了不必要且通常不需要的垃圾......它肯定会使阅读更容易。foo(char *a){a="hello";}foo(char *a){a[0]='h';a[1]='i';a[2]=0;}a
3赞 Pointy 11/28/2015
这是正确的 - 术语 pass-by-value 和 pass-by-reference 在编程语言设计中具有含义,而这些含义与对象突变完全无关。这完全取决于函数参数的工作原理。
2赞 Machtyn 2/23/2016
现在我明白了 obj1 = obj2 意味着 obj1 和 obj2 现在都指向相同的引用位置,如果我修改 obj2 的内部结构,引用 obj1 将公开相同的内部结构。如何复制一个对象,以便当我这样做时,源仍然是 {“id”:“1”}?source = { "id":"1"}; copy = source /*this is wrong*/; copy.id="2"
1赞 C Perkins 5/31/2017
我发布了另一个带有传统定义的答案,希望能减少混淆。“按值传递”和“按引用传递”的传统定义是在自动取消引用之前的内存指针时代定义的。完全可以理解的是,对象变量的值实际上是内存指针位置,而不是对象。尽管您对分配与突变的讨论可能有用,但没有必要抛弃传统术语或其定义。变更、赋值、按值传递、按引用传递等不得相互矛盾。
0赞 ebram khalil 6/16/2017
“数字”也是“不可变的”吗?
4赞 kazinix 9/16/2014 #11

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

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

    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; 
    }
    

评论

0赞 KJ Sudarshan 9/10/2021
你清楚地表达了我的理解。需要注意的主要事情是我们正在传递内容(参考或值)
3赞 John Sonderson 11/1/2014 #12

在 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;
}

评论

2赞 Danail Nachev 4/7/2015
我不认为JavaScript中是这种情况:'''javascript var num = 5;
1赞 Jörg W Mittag 6/1/2020
@DanailNachev:虽然这在技术上可能是正确的,但这种差异只能观察到可变对象,而 ECMAScript 基元则不然。
-3赞 lyslim 1/9/2015 #13

我会说它是逐个传递的——

考虑参数和变量对象是在函数调用开始时创建的执行上下文中创建的对象 - 传递到函数中的实际值/引用只是存储在此参数 + 变量对象中。

简单地说,对于基元类型,值在函数调用的开头被复制,对于对象类型,引用被复制。

评论

1赞 Scott Marcus 4/6/2018
“pass-by-copy” === 按值传递
27赞 Phil Mander 2/13/2015 #14

可以这样想:它总是按价值传递的。但是,对象的值不是对象本身,而是对该对象的引用。

下面是一个示例,传递一个数字(基元类型)

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}
98赞 geg 9/26/2015 #15

这些短语/概念最初是在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

评论

0赞 Michael Hoffmann 3/5/2017
额外学分;)的后续问题垃圾回收是如何工作的?如果我在一百万个值中循环一个变量,但一次只使用其中一个值,那么如何管理其他变量?当我将一个变量分配给另一个变量的值时会发生什么?那么我是指向指针,还是指向右操作数的指针?结果是指向 还是指向 ?{'George', 1}var myExistingVar = {"blah", 42}; var obj = myExistingVar;obj{"blah", 42}myExistingVar
0赞 geg 3/14/2017
@MichaelHoffmann 这些应该有自己的 SO 问题,并且可能已经得到了比我所能处理的更好的答案。话虽如此,我在浏览器开发工具中为循环函数(如您描述的函数)运行了一个内存配置文件,并在整个循环过程中看到了内存使用量的峰值。这似乎表明,在循环的每次迭代中确实创建了新的相同对象。当尖刺突然落下时,垃圾收集器只是清理了一组未使用的对象。1)
1赞 geg 3/14/2017
@MichaelHoffmann 关于类似的东西,javascript 没有提供使用指针的机制,因此变量永远不能指向指针(就像在 C 中一样),尽管底层 javascript 引擎无疑会使用它们。所以。。。将指向“右操作数的指针”2)var a = bvar a = ba
2赞 C Perkins 5/31/2017
没有必要忘记“通过引用/值传递”!这些术语具有历史含义,可以准确描述您试图描述的内容。如果我们抛弃历史术语和定义,懒得去了解它们的最初含义,那么我们就会失去代际之间有效沟通的能力。讨论不同语言和系统之间的差异是没有好办法的。相反,新程序员需要学习和理解传统术语以及它们为什么和来自哪里。否则,我们集体失去知识和理解。
1赞 Peter 5/2/2022
你的插图真的很有帮助 - 谢谢。
20赞 zangw 12/30/2015 #16

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

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

  • null
  • undefined
  • 字符串
  • 布尔
  • 符号 inES6

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

  • 对象
  • 阵 列
  • 功能

例如

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]

在上面的代码片段中,because 是一个标量基元,保存该值的一个初始副本,并为该值分配另一个副本。更改 时,绝不会更改 中的值。2abba

但两者都是对同一共享值的单独引用,这是一个复合值。需要注意的是,两者都不是“拥有”该值的,两者都只是对该值的平等对等引用。因此,当使用任一引用来修改()实际共享值本身时,它只影响一个共享值,并且两个引用都将引用新修改的值。cd[1,2,3]cd[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]

当我们进行赋值时,我们绝对不会做任何事情来影响仍然引用()的位置。要做到这一点,必须是一个指向而不是引用 -- 但 JS 中不存在这样的功能!b = [4,5,6]a[1,2,3]baarray

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]

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

若要通过值复制有效地传递复合值(如 ),需要手动创建该值的副本,以便传递的引用不会仍指向原始值。例如:array

foo( a.slice() );

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

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

var obj = {
    a: 2
};

foo( obj );

obj.a; // 42

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

评论

0赞 C Perkins 5/31/2017
您首先声明“复合值始终通过引用复制分配/传递”,然后声明“将引用的副本分配给 x”。对于所谓的“复合值”,实际变量值是引用(即内存指针)。正如您解释的那样,引用被复制了......因此,变量值被复制,再次强调 REFERENCE 是值。这意味着 JavaScript 对于所有类型都是按值传递的。按值传递意味着传递变量值的副本。该值是否是对对象/数组的引用并不重要。
0赞 Scott Marcus 4/6/2018
你引入了新的术语(value-copy/reference-copy),这只会让事情变得更加复杂。只有副本,句号。如果传递基元,则传递实际基元数据的副本,如果传递对象,则传递对象内存位置的副本。这就是你需要说的。任何更多的东西只会让人们更加困惑。
-1赞 Narayon 10/19/2016 #17

在低级语言中,如果要通过引用传递变量,则必须在创建函数时使用特定的语法:

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

这是对 的引用,但如果需要该值,则必须转换引用,使用 。&agemyAge*age

JavaScript 是一种高级语言,可以为您进行此转换。

因此,尽管对象是通过引用传递的,但语言会将引用参数转换为值。您不需要在函数定义上使用 ,通过引用传递它,也不需要在函数体上使用 ,将引用转换为值,JavaScript 会为您完成。&*

这就是为什么当你尝试通过替换它的值(即)来更改函数中的对象时,更改不会持续存在,但如果你更改它的属性(即),它会保留。age = {value:5}age.value = 5

了解更多信息

评论

0赞 Peter Mortensen 10/16/2020
不,没有必要(或允许)取消引用 C++ 中的引用。要么是取消引用的指针,要么是未取消引用的引用。
0赞 gman 12/16/2020
这个答案实际上是错误的。JavaScript 不执行此类转换。在 JavaScript 中无法通过引用传递。“通过引用传递”的全部意义在于让函数能够更改 的值。你不能在 JavaScript 中做到这一点。您可以更改对象引用的属性,但不能不更改变量本身。这就是“通过引用传递”的意思,能够更改函数外部变量的值。myAgemyAgemyAge
0赞 steviesh 10/29/2016 #18

我已经多次阅读了这些答案,但直到我了解了芭芭拉·里斯科夫(Barbara Liskov)所说的“共享呼叫”的技术定义,我才真正理解它

通过共享调用的语义与通过引用调用的不同之处在于,调用者看不到函数中函数参数的赋值(与引用语义不同)[需要引用],因此,例如,如果传递了一个变量,则不可能在调用方的作用域中模拟该变量的赋值。但是,由于函数可以访问与调用方相同的对象(不进行复制),因此调用方可以看到函数中这些对象的突变(如果对象是可变的),这可能与按值语义调用不同。函数中可变对象的突变对调用者是可见的,因为该对象不是复制或克隆的,而是共享的。

也就是说,如果您访问参数值本身,则参数引用是可更改的。另一方面,对参数的赋值将在计算后消失,并且函数调用者无法访问。

评论

0赞 Scott Marcus 4/6/2018
不,对象是否可变并不是真正的问题。一切都是按值传递的。它只取决于你传递的内容(值或引用)。看这个
0赞 Sanjeev 6/18/2019
她所描述的是传递一个引用 BY-VALUE。没有理由引入新术语。
6赞 C Perkins 12/24/2016 #19

语义学!!设定具体的定义必然会使一些答案和评论不兼容,因为即使使用相同的单词和短语,它们也不会描述相同的事物,但克服混淆至关重要(尤其是对于新程序员)。

首先,似乎不是每个人都能掌握多个抽象层次。学习过第 4 代或第 5 代语言的新程序员可能很难将他们的思想集中在汇编或 C 程序员熟悉的概念上,而不是通过指针到指针到指针进行分阶段。按引用传递并不仅仅意味着使用函数参数变量更改引用对象的能力。

变量:符号的组合概念,它引用内存中特定位置的值。这个术语通常太重了,不能单独用于讨论细节。

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

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

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

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

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

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

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

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

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

调用/引用传递:函数参数的值可以/将直接由相应的函数参数更新。如果有帮助,函数参数将成为参数的有效“别名”——它们有效地引用同一内存位置的相同值。如果函数参数是对象变量,则更改对象数据的能力与按值传递的情况没有什么不同,因为函数参数仍将指向与参数相同的对象。但是在对象变量的情况下,如果函数参数被设置为一个完全不同的对象,那么参数同样也会指向不同的对象——这在按值传递的情况下不会发生。

JavaScript 不通过引用传递。如果你仔细阅读,你会发现所有相反的观点都误解了按值传递的含义,他们错误地得出结论,通过函数参数更新对象数据的能力是“按值传递”的同义词。

对象克隆/复制:创建一个新对象并复制原始对象的数据。这可以是深拷贝或浅拷贝,但关键是要创建一个新对象。创建对象的副本是与按值传递不同的概念。某些语言区分类对象和结构(或类似语言),并且可能具有不同的传递不同类型的变量的行为。但是 JavaScript 在传递对象变量时不会自动执行此类操作。但是,没有自动对象克隆并不意味着通过引用传递。

2赞 DannyNiu 7/28/2017 #20

对于编程语言律师,我已经阅读了 ECMAScript 5.1 的以下部分(它比最新版本更容易阅读),甚至在 ECMAScript 邮件列表上询问它。

TL的;DR:一切都是按值传递的,但 Object 的属性是引用,而 Object 的定义在标准中非常缺乏。

参数列表的构建

第 11.2.4 节 “参数列表” 在生成仅包含 1 个参数的参数列表时说了以下内容:

生产 ArgumentList : AssignmentExpression 的计算方式如下:

  1. 设 ref 是计算 AssignmentExpression 的结果。
  2. 设 arg 为 GetValue(ref)。
  3. 返回一个 List,其唯一项是 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 模式代码中,则 keep 为 true,否则让 strict 为 false。
  8. 返回一个 Reference 类型的值,其基值为 baseValue,引用的名称为 propertyNameString,其 strict 模式标志为 strict。

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

参考

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

“对象”的定义

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

评论

0赞 David A. Gray 10/1/2017
令我惊讶的是,有多少人对值传递的参数、引用传递的参数、对整个对象的操作以及对其属性的操作之间的区别感到困惑。1979 年,我没有获得计算机科学学位,而是选择在我的 MBA 课程中增加 15 小时左右的 CS 选修课。然而,我很快就明白,我对这些概念的理解至少与我任何拥有计算机科学或数学学位的同事一样好。学习汇编程序,它会变得很清楚。
0赞 Jonas Wilms 7/26/2020
规范中的引用与相关行为无关。它是一个中间构造,用于解释为什么能够知道属性()在哪个对象()上设置(原因计算结果为)。a.b = 1aba.bReference { a, "b" }
6赞 Zameer Ansari 8/10/2017 #21

分享我对 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赞 Quentin 8/10/2017
这是一个过于简单的答案,它没有说明早期的答案没有更好地解释。我很困惑为什么你把数组作为一个特例来调用。
1赞 RobG 7/27/2019
"对象存储为引用“具有误导性。我认为您的意思是,当将对象分配给变量时,分配给变量的值是对对象的引用。
0赞 amaster 4/11/2020
这并不能解决更新函数内部的对象而不更新函数外部对象的问题。这就是整个画面,它似乎是作为值而不是参考起作用的。因此 -1
0赞 Zameer Ansari 4/11/2020
@amaster 谢谢你指出这一点!你能建议一个编辑吗?
0赞 amaster 4/13/2020
哈哈,我试过了......我建议的编辑更改了太多 AMD 是不允许的
4赞 georgeawg 7/25/2018 #22

JavaScript 按值传递基元类型,按引用传递对象类型

现在,人们喜欢无休止地争吵是否“通过引用” 是描述 Java 等人实际操作的正确方式。重点 是这样的:

  1. 传递对象不会复制该对象。
  2. 传递给函数的对象可以由函数修改其成员。
  3. 传递给函数的基元值不能由函数修改。制作副本。

在我的书中,这叫做“引用传递”。

Brian Bi - 哪些编程语言是按引用传递的?


更新

这是对这一点的反驳:

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

评论

0赞 nasch 3/28/2019
@Amy 因为这是描述按值传递,而不是按引用传递。这个答案是一个很好的答案,显示了差异:stackoverflow.com/a/3638034/3307720
0赞 3/28/2019
@nasch我明白其中的区别。#1 和 #2 描述了 pass-by-ref 语义。#3 描述了按值传递的语义。
0赞 nasch 3/28/2019
@Amy 1、2 和 3 都与传递值一致。要通过引用传递,您还需要 4:将引用分配给函数内的新值(使用 = 运算符)也会重新分配函数外部的引用。Javascript 并非如此,它只按值传递。传递对象时,将指针传递到该对象,然后按值传递该指针。
0赞 3/28/2019
这通常不是“通过引用”的含义。你已经满足了我的询问,我不同意你的看法。谢谢。
0赞 Jörg W Mittag 6/1/2020
“在我的书中,这叫做通过引用传递”——在每一本编译器书、解释器书、编程语言理论书和计算机科学书中,它都不是。
10赞 Ashish Singh Rawat 9/26/2018 #23

这是对按值传递和按引用传递 (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 引擎将对象分配给变量 ,它指向一些内存,比如 (0x012)。c
  • 当 d=c 时,在此步骤中指向相同的位置 (0x012)。d
  • 更改任何变量的值都会更改两个变量的值。
  • 函数是对象

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

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

评论

0赞 georgeawg 3/28/2019
在所谓的特殊情况下,导致内存空间分配的不是赋值运算符,而是对象文本本身。卷曲括号表示法会导致新对象的创建。该属性设置为新对象引用的副本。c
0赞 gman 12/16/2020
这不是通过引用传递的。这是按值传递的,谁的值恰好是引用。
5赞 miguelr 4/20/2019 #24

MDN 文档对此进行了清晰的解释,但不会太冗长:

函数调用的参数是函数的参数。 参数按值传递给函数。如果功能更改 参数的值,此更改不会全局反映在全局或 调用函数。但是,对象引用也是值,并且 它们很特殊:如果函数更改了被引用对象的 属性,该更改在函数外部可见,(...)

来源:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Description

18赞 TechDogLover OR kiaNasirzadeh 11/14/2019 #25

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

在JavaScript中,我们可以将值放在两层中:type1-和type2-所有其他类型的值,如&&等objectsstringboolean

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

enter image description here

每个 type2 值(绿色)都是一个正方形,而 type1 值(蓝色)是一组正方形

enter image description here

关键是,如果你想指示一个 type2-value,地址是普通的,但如果你想对 type1-value 做同样的事情,这并不容易!:

enter image description here

在一个更复杂的故事中:

enter image description here

所以这里的参考资料可以拯救我们:
enter image description here

虽然这里的绿色箭头是一个典型的变量,但紫色的箭头是一个对象变量,所以因为绿色箭头(典型变量)只有一个任务(即表示一个典型值),我们不需要将它的值与它分开,所以我们将绿色箭头与它的值一起移动到任何地方和所有赋值中, 功能等等......

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

一个非常令人困惑的情况是,你无法意识到你引用的变量是如何变化的,让我们看一个很好的例子:

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'];

enter image description here enter image description here

评论

0赞 user31782 8/24/2022
我在任何图片中都找不到值“6”。
0赞 TechDogLover OR kiaNasirzadeh 9/4/2022
@user31782这是我的错别字,请原谅:)
0赞 Ethan Standel 10/5/2022
我无法克服的是假装字符串“john”可以很好地放在一个小的记忆方块中,就像数字 21 一样。John 仍然是引擎盖下的一组 char,因此您拥有的任何引用仍然指向同一个地方。如果你修改了字符串,那么你会得到一个副本,但严格来说,因为 JavaScript 中没有可变的字符串操作。
6赞 Jonas Wilms 7/26/2020 #26

观察:如果观察者无法检查引擎的底层内存,则无法确定是复制不可变值还是传递引用。

JavaScript 或多或少与底层内存模型无关。没有参考²这样的东西。JavaScript 有。两个变量可以保存相同的值(或者更准确地说:两个环境记录可以绑定相同的)。唯一可以改变的值类型是通过其抽象 [[Get]] 和 [[Set]] 操作的对象。 如果你忘记了计算机和内存,这就是你描述JavaScript行为所需要的一切,它可以让你理解规范。

 let a = { prop: 1 };
 let b = a; // a and b hold the same value
 a.prop = "test"; // The object gets mutated, can be observed through both a and b
 b = { prop: 2 }; // b holds now a different value

现在你可能会问自己,两个变量如何在计算机上保持相同的值。然后,你可能会查看 JavaScript 引擎的源代码,你很可能会找到一些被编写引擎的语言的程序员称为引用的东西。

所以事实上,你可以说 JavaScript 是“按值传递”,而值是可以共享的,你可以说 JavaScript 是“按引用传递”,这对于来自低级语言的程序员来说可能是一个有用的逻辑抽象,或者你可以称这种行为为“通过共享调用”。

由于 JavaScript 中没有引用这样的东西,所有这些都既没有错,也没有正确。因此,我认为搜索答案不是特别有用。

² 规范中的术语“参考”不是传统意义上的参考。它是对象和属性名称的容器,并且是一个中间值(例如,计算结果为 )。术语“参考”有时也会出现在规范中不相关的部分。a.bReference { value = a, name = "b" }

-1赞 TomoMiha 10/11/2021 #27

如果您想要像其他语言一样的(正常)函数参数行为(传递值的副本) 然后只需在传入函数之前克隆对象:

function run()
{
    var test = [];
    test.push(1);

    console.log('before: '+test); // 1

    changeVariable(_.clone(test)); // (Note: I am using lodash _.clone() function)
 
    console.log('after: '+test); // 1 
}


function changeVariable(test2) {
  var test1 = test2;
  test1.push(2); 
  console.log('inside func:', test1);  // inside func: [1,2]
}   


run();    
7赞 Gianluca Ghettini 11/5/2021 #28

一切都是按值传递的。

基本类型按值传递(即将实际变量值的新副本传递给函数)。

复杂类型(对象)作为“指向对象的指针”传递。因此,您传递的实际内容是按值传递的指针(它是一个地址,一个像其他任何值一样的数值)。显然,如果您尝试在函数内修改对象的属性,则即使在该函数之外也会反映修改。这是因为您通过指向属性的唯一副本的指针访问该属性。

“按值传递指针”和“按引用传递对象”是一回事。

0赞 Holger Seelig 6/8/2023 #29
function passByCopy ([...array], {...object})
{
   console .log ("copied objects", array, object)
}

passByCopy ([1,2,3], {a:1, b:2, c:3})

function passByReference (array, object)
{
   console .log ("same objects", array, object)
}

passByReference ([1,2,3], {a:1, b:2, c:3})

评论

1赞 Super Kai - Kazuya Ito 6/9/2023
您应该添加解释。
0赞 trincot 9/10/2023 #30

引用提问者的话:

基元类型(数字、字符串等)是按值传递的,但对象 [...$ 既可以按值传递(在这种情况下,我们认为包含对象的变量实际上是对对象的引用)也可以按引用传递(当我们认为对象的变量包含对象本身时)。

事实并非如此。只有一种方法可以将对象作为参数传递。尽管其他答案也区分了原始值和非原始值,但这是一种干扰和无关紧要。

无引用传递

考虑以下代码,它不关心是原语还是对象:a

function alter(arg) {  /* some magic happening here to `arg` */ }

function main() {
    var a, b;
    a = b = { x: 1 }; // Example value. It can be an object or primitive: irrelevant here
    console.log(a === b); // true
    alter(a);
    console.log(a === b); // false? (not possible)
}

如果函数有某种方式进行第二个输出,那么我们可以说是按引用传递,因为那时将是 的别名。但这在 JavaScript 中是不可能的。原语的概念与此无关:JavaScript 中没有按引用传递。alterfalsearga

对象

在 JavaScript 中,对象是对属性(和插槽)集合的引用。当使用对象作为参数调用函数时,这不会创建新的属性集合。函数的参数变量(即局部变量)将接收相同的值(即对属性集合的引用)。

变量赋值与对象突变

调用方的数据结构也可以通过函数调用进行更改。这显然只适用于可变值(根据定义),在 JavaScript 中,这是通过为属性赋值来实现的。要区分两件事:

  • 对 objext 属性的赋值会改变调用方和被调用方都有引用的对象。
  • 对参数变量的赋值不会改变调用方的数据。具体而言,如果该参数变量是对象(引用),则该引用将被覆盖,因此函数将自身与调用方的数据分离