JavaScript 是否通过引用传递?[复制]

Does JavaScript pass by reference? [duplicate]

提问人:J Any 提问时间:10/28/2012 最后编辑:Peter MortensenJ Any 更新时间:10/16/2020 访问量:280630

问:

JavaScript 是通过引用传递还是通过值传递?

下面是 JavaScript 中的一个示例:The Good Parts。我对矩形函数的参数感到非常困惑。它实际上是 ,并在函数中重新定义。没有原始参考资料。如果我从函数参数中删除它,则内部区域函数将无法访问它。myundefined

是关闭吗?但不返回任何函数。

var shape = function (config) {
    var that = {};
    that.name = config.name || "";
    that.area = function () {
        return 0;
    };
    return that;
};

var rectangle = function (config, my) {
    my = my || {};
    my.l = config.length || 1;
    my.w = config.width || 1;
    var that = shape(config);
    that.area = function () {
        return my.l * my.w;
    };
    return that;
};

myShape = shape({
    name: "Unhnown"
});

myRec = rectangle({
    name: "Rectangle",
    length: 4,
    width: 6
});

console.log(myShape.name + " area is " + myShape.area() + " " + myRec.name + " area is " + myRec.area());
JavaScript 逐个引用 传递值

评论


答:

873赞 Alnitak 10/28/2012 #1

基元按值传递,对象按“引用副本”传递。

具体来说,当你传递一个对象(或数组)时,你(无形地)传递了对该对象的引用,并且可以修改该对象的内容,但如果你试图覆盖引用,它不会影响调用者持有的引用的副本 - 即引用本身是按值传递的:

function replace(ref) {
    ref = {};           // this code does _not_ affect the object passed
}

function update(ref) {
    ref.key = 'newvalue';  // this code _does_ affect the _contents_ of the object
}

var a = { key: 'value' };
replace(a);  // a still has its original value - it's unmodfied
update(a);   // the _contents_ of 'a' are changed

评论

43赞 Ioan Alexandru Cucu 2/1/2014
虽然不流行,但对象的行为被直观地命名为“共享呼叫”:en.wikipedia.org/wiki/Call_by_sharing#Call_by_sharing
8赞 Alnitak 2/3/2014
@IoanAlexandruCucu我个人认为“参考副本”更直观;-)
9赞 Alnitak 8/17/2014
@Inverse 我回滚了您的编辑,因为它完全改变了它的语义。对如此高票数的答案进行如此重大的更改也是完全不合适的!
1赞 user3015682 5/12/2015
ref = {} 与 ref = new Object() 相同,所以我不会称它为替换,而是创建一个同名的局部变量,并且局部名称优先于全局名称。在该替换函数中,您可以执行 window.ref 来访问从未实际替换过的全局 ref,因为即使在您离开该函数后,它仍保留其原始值。虽然这个答案可能是正确的,但我认为对它为什么有效的一些解释会有所帮助。
16赞 gman 8/14/2016
如果你打算使用短语“引用副本”,那么你不妨将基元称为“值副本”。这就是为什么两者实际上都只是“按价值传递”。两者都传递值的副本,无论该值是数字、布尔值、字符串还是引用。
73赞 jAndy 10/28/2012 #2

可以这样想:

每当您在 ECMAscript 中创建一个对象时,这个对象都会在一个神秘的 ECMAscript 通用位置形成,没有人能够获得。你得到的只是在这个神秘的地方对那个物体的引用

var obj = { };

Even只是对对象(位于那个特别美妙的地方)的引用,因此,您只能传递此引用。实际上,任何访问 obj 的代码段都会修改很远很远的对象obj

评论

47赞 Pointy 10/28/2012
引用本身是通过值传递的,就像 JavaScript 中的其他所有内容一样。
2赞 albanx 3/10/2019
@Pointy引用的价值是什么?引用是一种类型吗?我认为这个文字游戏是没用的。
2赞 Pointy 3/10/2019
@albanx我意识到这很令人沮丧,但每个专业领域都有专门的术语。“引用”的意思是类似于 C 或 C++ 中“指针”的含义(好吧,C++ 同时具有指针引用)。然而,在JavaScript或Java等语言中,作为特定对象的“值”只能是对该对象的引用。所以它不是一种类型,实际上,它是对价值实际是什么的表征。
2赞 Pointy 3/10/2019
@albanx我会把这个意见转达给2012年的我:)
5赞 Pointy 3/11/2019
@jAndy,在这次交流中,我被一种有点病态的认识所震惊,即在我离开这个世界很久之后,人们还会对我的答案和评论发表评论。
22赞 jmoreno 10/28/2012 #3

与 C 一样,最终,一切都是通过值传递的。与 C 语言不同,您实际上无法备份和传递变量的位置,因为它没有指针,只有引用。

它所拥有的引用都是对象,而不是变量。有几种方法可以达到相同的结果,但它们必须手动完成,而不仅仅是在调用或声明站点添加关键字。

评论

3赞 joekarl 7/19/2014
这实际上是这里最正确的答案。如果你曾经深入研究过 V8 或竞争引擎,这就是函数调用的实际实现方式。
0赞 user3015682 5/12/2015
在幕后,我敢打赌对象是指针。对象参数是新创建的指针,它指向与传入的指针相同的地址。
5赞 Pete Campbell 11/23/2013 #4

实际上,Alnitak 是正确的,并且易于理解,但最终在 JavaScript 中,一切都是通过值传递的。

对象的“价值”是什么?它是对象引用。

当你传入一个对象时,你会得到这个值的副本(因此是 Alnitak 描述的“引用副本”)。如果更改此值,则不会更改原始对象;您正在更改该引用的副本。

评论

5赞 Guillermo Siliceo Trueba 1/13/2014
这并不能澄清,而是令人困惑。
0赞 Can Mingir 8/18/2020
“一个物体的'价值'是什么?它是对象引用。简单而完美!
9赞 yallam 3/8/2014 #5

JavaScript 是按值传递的。

对于基元,将传递基元的值。对于 Objects,将传递 Object 的引用“value”。

Object 示例:

var f1 = function(inputObject){
    inputObject.a = 2;
}

var f2 = function(){
    var inputObject = {"a": 1};
    f1(inputObject);
    console.log(inputObject.a);
}

调用 f2 会导致将“a”值打印为 2 而不是 1,因为传递了引用并更新了引用中的“a”值。

使用基元的示例:

var f1 = function(a){
    a = 2;
}
var f2 = function(){
    var a = 1;
    f1(a);
    console.log(a);
}

调用 f2 会导致将“a”值打印为 1。

3赞 tony41780 7/25/2014 #6

“全局”JavaScript 变量是 window 对象的成员。您可以作为窗口对象的成员访问引用。

var v = "initialized";

function byref(ref) {
  window[ref] = "changed by ref";
}

byref((function(){for(r in window){if(window[r]===v){return(r);}}})());
// It could also be called like... byref('v');
console.log(v); // outputs changed by ref

请注意,上面的示例不适用于函数中声明的变量。

70赞 Ray Perea 8/7/2014 #7

我的两分钱......JavaScript 是通过引用还是值传递参数无关紧要。真正重要的是分配与突变。

我在这个链接中写了一个更长、更详细的解释

当你传递任何东西(无论是对象还是原语)时,JavaScript 所做的只是在函数内部分配一个新变量......就像使用等号()一样。=

该参数在函数中的行为方式与您刚刚使用等号分配一个新变量时的行为完全相同......举个简单的例子。

var myString = 'Test string 1';

// Assignment - A link to the same place as myString
var sameString = myString;

// If I change sameString, it will not modify myString,
// it just re-assigns it to a whole new string
sameString = 'New string';

console.log(myString); // Logs 'Test string 1';
console.log(sameString); // Logs 'New string';

如果我将参数作为参数传递给函数,它的行为就好像我只是将其分配给一个新变量一样。现在,让我们做同样的事情,但使用函数而不是简单的赋值myString

function myFunc(sameString) {

  // Reassignment... Again, it will not modify myString
  sameString = 'New string';
}

var myString = 'Test string 1';

// This behaves the same as if we said sameString = myString
myFunc(myString);

console.log(myString); // Again, logs 'Test string 1';

在将对象传递给函数时可以修改对象的唯一原因是,您没有重新分配...相反,对象可以被改变或改变。同样,它的工作方式相同。

var myObject = { name: 'Joe'; }

// Assignment - We simply link to the same object
var sameObject = myObject;

// This time, we can mutate it. So a change to myObject affects sameObject and visa versa
myObject.name = 'Jack';
console.log(sameObject.name); // Logs 'Jack'

sameObject.name = 'Jill';
console.log(myObject.name); // Logs 'Jill'

// If we re-assign it, the link is lost
sameObject = { name: 'Howard' };
console.log(myObject.name); // Logs 'Jill'

如果我将参数作为参数传递给函数,它的行为就好像我只是将其分配给一个新变量一样。同样,同样的事情具有完全相同的行为,但具有函数。myObject

function myFunc(sameObject) {
  // We mutate the object, so the myObject gets the change too... just like before.
  sameObject.name = 'Jill';

  // But, if we re-assign it, the link is lost
  sameObject = {
    name: 'Howard'
  };
}

var myObject = {
  name: 'Joe'
};

// This behaves the same as if we said sameObject = myObject;
myFunc(myObject);
console.log(myObject.name); // Logs 'Jill'

每次将变量传递给函数时,您都是在“赋值”参数的名称,就像使用等号一样。=

永远记住,等号表示分配。 将参数传递给函数也意味着赋值。 它们是相同的,并且两个变量以完全相同的方式连接。=

修改变量影响其他变量的唯一情况是基础对象发生突变时。

区分对象和基元是没有意义的,因为它的工作方式与你没有函数而只是使用等号分配给新变量的方式完全相同。

评论

2赞 GL_Stephen 3/17/2017
“通过副本传递”和“通过引用传递”就这么简单,可以传达所有相关的含义。我得到的是“一个东西是它自己的东西”,还是“东西”就是你所关心的。
1赞 barlop 7/11/2019
你的作业(没有&),类比似乎只是对传递值的解释,不是吗?那么为什么不这么说呢?为什么说按值传递是无关紧要的,当你在谈论按值传递时
3赞 jayvatar 9/9/2020
很好的解释雷!
0赞 Synetech 8/10/2022
Ray Perea>我的两分钱......JavaScript 是通过引用还是值传递参数无关紧要。真正重要的是分配与突变。- GL_Stephen> “通过副本传递”和“通过引用传递”就这么简单,以传达所有相关的含义。嗯,你们意识到这里也存在性能问题,对吧?传递包含按值的对象数组的大量对象比按引用传递要慢得多。😒
41赞 user6445533 7/23/2016 #8

函数参数是按值传递的,也可以是按共享传递的,但在 JavaScript 中从来不会通过引用传递!

按值调用

基元类型按值传递:

var num = 123, str = "foo";

function f(num, str) {
  num += 1;
  str += "bar";
  console.log("inside of f:", num, str);
}

f(num, str);
console.log("outside of f:", num, str);

函数作用域内的重新分配在周围作用域中不可见。

这也适用于 s,它是一种复合数据类型,但不可变:String

var str = "foo";

function f(str) {
  str[0] = "b"; // doesn't work, because strings are immutable
  console.log("inside of f:", str);
}

f(str);
console.log("outside of f:", str);

共享呼叫

对象,即所有不是基元的类型,都是通过共享传递的。保存对对象的引用的变量实际上仅包含此引用的副本。如果 JavaScript 采用按引用调用的计算策略,则变量将保留原始引用。这是按共享和按引用之间的关键区别。

这种区别的实际后果是什么?

var o = {x: "foo"}, p = {y: 123};

function f(o, p) {
  o.x = "bar"; // Mutation
  p = {x: 456}; // Reassignment
  console.log("o inside of f:", o);
  console.log("p inside of f:", p);
}

f(o, p);

console.log("o outside of f:", o);
console.log("p outside of f:", p);

Mutating 意味着修改现有 .变量绑定到的引用副本以及引用此对象的引用副本保持不变。因此,在调用方的作用域中可以看到突变。Object

重新赋值意味着替换绑定到变量的引用副本。由于它只是一个副本,因此保存同一引用副本的其他变量不受影响。因此,重新分配在呼叫者的作用域中不可见,就像使用引用调用评估策略一样。

有关 ECMAScript 中评估策略的更多信息。

评论

0赞 Eric 9/4/2022
这是我见过的关于Javascript的最好和最重要的答案之一。
0赞 Eric 9/4/2022
它不仅适用于函数调用,也适用于简单的赋值。
5赞 4m1r 8/23/2017 #9

为了创建一个使用 const 的简单示例......

const myRef = { foo: 'bar' };
const myVal = true;

function passes(r, v) {
  r.foo = 'baz';
  v = false;
}

passes(myRef, myVal);

console.log(myRef, myVal); // Object {foo: "baz"} true
3赞 Paulo Buchsbaum 3/10/2018 #10

如果没有纯粹主义,我认为在 JavaScript 中通过引用模拟标量参数的最佳方法是使用对象,就像前面的答案一样。

但是,我做的有点不同:

我已经在函数调用中进行了对象赋值,因此可以在函数调用附近看到引用参数。它提高了源的可读性。

在函数声明中,我把属性放在注释中,原因与此相同:可读性。

var r;

funcWithRefScalars(r = {amount:200, message:null} );
console.log(r.amount + " - " + r.message);


function funcWithRefScalars(o) {  // o(amount, message)
  o.amount  *= 1.2;
  o.message = "20% increase";
}

在上面的示例中,清楚地指示了输出参考参数。null

出口:

240 - 20% Increase

在客户端,应替换为 。console.logalert

★ ★ ★

另一种可读性更强的方法:

var amount, message;

funcWithRefScalars(amount = [200], message = [null] );
console.log(amount[0] + " - " + message[0]);

function funcWithRefScalars(amount, message) {  // o(amount, message)
   amount[0]  *= 1.2;
   message[0] = "20% increase";
}

在这里,您甚至不需要创建新的虚拟名称,如上所述。r

1赞 michal.jakubeczy 1/24/2019 #11

基元按值传递。但是,如果您只需要读取原始值(并且在调用函数时不知道值),则可以传递函数,该函数会在您需要时检索值。

function test(value) {
  console.log('retrieve value');
  console.log(value());
}

// call the function like this
var value = 1;
test(() => value);
2赞 Juhani 1/30/2019 #12

在人们试图证明这一点的例子中,我看不到通过引用。我只看到按值传递

对于包含对对象的引用的变量,引用是这些变量的值,因此传递引用,然后按值传递

在这样的声明中,

var a = {
  b: "foo",
  c: "bar"
};

“a”的值不是 Object,而是(到目前为止)对它的引用。换句话说,对象不在变量中 - 对它的引用在变量中。我认为这对于主要只熟悉 JavaScript 的程序员来说似乎很困难。但对于也知道 Java、C# 和 C 的人来说,这很容易。a

2赞 user3015682 5/31/2019 #13

对象始终按引用传递,基元始终按值传递。只需将该参数保留在对象的同一地址即可。

这里有一些代码来说明我的意思(在 https://js.do/ 等 JavaScript 沙箱中尝试一下)。

不幸的是,您不能只保留参数的地址;您还将保留所有原始成员值。

a = { key: 'bevmo' };
testRetain(a);
document.write(' after function ');
document.write(a.key);


function testRetain (b)
{
    document.write(' arg0 is ');
    document.write(arguments[0].key);
    b.key = 'passed by reference';
    var retain = b; // Retaining the original address of the parameter

    // Address of left set to address of right, changes address of parameter
    b = {key: 'vons'}; // Right is a new object with a new address
    document.write(' arg0 is ');
    document.write(arguments[0].key);

    // Now retrieve the original address of the parameter for pass by reference
    b = retain;
    document.write(' arg0 is ');
    document.write(arguments[0].key);
}

结果:

arg0 是 bevmo arg0 是 vons arg0 在通过引用传递的函数之后通过引用传递