【问题标题】:Assigning to a new value to an object passed into a function in JavaScript为传递给 JavaScript 函数的对象分配新值
【发布时间】:2014-03-21 23:07:40
【问题描述】:

我是 JavaScript 新手(虽然在 C++ 方面有经验),今天我写了这样的东西:

function foo(bar) {
    bar = "something else";
}
var x = "blah";
foo(x);
alert(x); // Alerts with "blah", but I was expecting it to alert with "something else"

这让我很困惑,因为我一直在观看 Douglas Crockford 的一些 JavaScript 视频,记得他说过“JavaScript 总是通过引用传递”之类的话。

我可以解释这种情况的方式是 JavaScript 传递对对象的引用,但这些引用被复制了。这意味着在foo 函数中,我为bar 分配了一个新引用,然后它超出了范围,留下引用x 保持不变。基本上我们从:

x   ---->"blah"

那么当foo被调用时,bar引用相同的数据:

x   ---->"blah"
bar -----^

所以当“别的东西”被分配给bar时,就会发生这种情况:

x   ---->"blah"
bar ---->"something else"

这是 JavaScript 中实际发生的事情的准确模型,还是我遗漏了其他东西?

作为一个额外的问题,有没有办法说,改变这个变量引用的数据?这是经常出现的情况,还是可以轻松避免的?

编辑:

Douglas Crockford 在video I watched 中说“对象总是通过引用传递,而不是通过值传递”,这是正确的,但是函数的参数是通过值传递的,只是引用是通过值传递的。

【问题讨论】:

标签: javascript pass-by-reference pass-by-value


【解决方案1】:

你的解释是正确的。

首先,您有一个名为x 的变量,它是一个字符串对象的引用。假设内存是 0x100。 x 指向0x100,其中包含字节blah

var x = "blah"; // x is 0x100 which references a string in memory

接下来,将0x100 传递给函数foo

function foo(bar) {
    bar = "something else";
}

由于 JavaScript 中的所有内容都是按值传递的,甚至是引用,JavaScript 会在内存中复制此引用,现在在该函数中将其称为 bar

foo(x); // Copies the value of x (a reference) to bar

此时,我们有两个独立的变量。 xbar。两者碰巧具有相同的值,0x100。因此,如果您要更改其中任何一个正在引用的对象的属性,它将同时影响xbar

然而,你正在做的是分配bar指向别的东西:

bar = "something else"; // Now references some other string we just created

现在,bar 被重新分配以引用我们刚刚为其分配内存的新字符串。 bar 不再具有 0x100 的值,它现在具有某个其他地址的值(例如 0x500)。 x 当然 仍然0x100 的值,因为 bar 只是 x 的副本,而不是对 x 的引用。

因此,当您:

alert(x);

你仍然会得到原始值,因为那是 x 所指向的。

第二个问题:

有没有办法说,改变这个变量引用的数据? 这是经常出现的情况,还是可以轻松避免的?

是的,只需将其包裹在另一个对象中即可。例如:

var x = {Value: "blah"};
foo(x);

现在,我们有一个对象的引用,该对象的属性名为Value,其中包含对内存中某处某个字符串的引用。

foo,我们可以这样做:

bar.Value = "something else";

这会影响xValue 属性,因为barx 都引用了同一个对象,而您从未更改过其中任何一个的值。

换句话说,您不能重新分配您传递给函数的引用,因为您只是重新分配一个副本。但是,您可以更改被引用对象的属性,因为该引用的其他副本都指向您正在更改的数据。

【讨论】:

    【解决方案2】:

    你的解释是正确的。

    您可以更改对象中键的值,这使您可以执行类似于 pass-by-reference 的操作:

    function foo(bar) {
      bar.msg = "something else";
    }
    var x = { msg: "blah" };
    foo(x);
    alert(x.msg);
    

    【讨论】:

    • 更准确地说是传递一个引用,实际上是按值传递(就像JavaScript中的一切)
    【解决方案3】:

    Douglas Crockford 在我观看的视频中说“物体总是 通过引用传递它们不是按值传递”,这是正确的, 但是函数的参数是按值传递的,它只是 引用是按值传递的。

    这是不正确的。所描述的内容精确地称为按值传递。 JavaScript 和 Java 一样,只有值传递。没有传递引用。

    你的理解是正确的。 JavaScript 中的每个值要么是原始值,要么是引用(指向对象的指针)。对象本身永远不能直接成为值。当您传递或分配引用(指向对象的指针)时,指针的新副本将看到与原始指针相同的对象。但它们仍然是两个不同的指针变量。

    【讨论】:

      【解决方案4】:

      我知道你已经用你的编辑回答了你自己的问题......

      Douglas Crockford 在我观看的视频中说“对象总是通过引用传递,而不是通过值传递”,这是正确的,但是函数的参数是通过值传递的,只是引用是通过值传递的。

      但是因为那个编辑让我突然明白了它是如何工作的......我承认我在这里苦苦挣扎的是一个代码示例和笔,我认为它真正展示了它。

      x = {};
      y = x; //point y reference to same object x is pointed to
      
      console.log(x === y) //true both pointed at same object
      
      function funky(o) //pass by value a reference to object
      {
        o = null; //change reference to point to null away from object
      }
      
      funky(x);
      
      console.log(x)//not null still an object
      
      var myObj = {
        value: "Hello"
      };
      
      function change(localObj) {//pass reference to object
        localObj.value = "Bye";//change property through the reference
        localObj = null;//point the reference to null away from object
      }
      
      console.log(x === y) //still same reference;
      change(myObj);
      console.log(myObj.value) // Prompts "Bye"
      console.log(myObj) //not null - its an object;
      x = myObj; //point x to different object
      console.log(x === y) //false pointed at different object;
      console.log(x);
      console.log(y);
      

      https://codepen.io/waynetheisinger/pres/YewMeN

      【讨论】:

        【解决方案5】:

        “JavaScript 总是通过引用传递”是一个[白色] 谎言 和一个混淆 术语。虽然有一些“灰色地带”,但我会选择these definitions of evaluation strategies

        这是我的论点和推理。如果您持有不同的观点,请确保您至少可以支持它。

        Call By Reference

        通过引用调用意味着(对许多人)分配给参数影响调用者中的绑定。

        在按引用调用评估(也称为按引用传递)中,函数接收对用作参数的变量的隐式引用,而不是其值的副本。 这通常意味着函数可以修改(即分配给)用作参数的变量——调用者会看到的东西。

        不是在 JavaScript 中的情况,正如原始帖子在观察到的行为中所指出的那样。重新分配参数(可以将其视为具有动态提供值的局部变量)不会影响任何提供的参数。

        有时,“引用调用”[令人困惑地]用于表示“共享调用”或“[引用的]值调用”,如下所述;真正的引用调用存在于 C++ 和 VB 等语言中,而不是 JavaScript。

        Call By [Object] Sharing

        JavaScript 的调用约定可以完全按照Call By [Object] Sharing 语义来讨论。

        所有 JavaScript 对象都是值;并且所有原始值(所有值的子集)都是不可变的。

        共享调用的语义不同于引用调用,因为调用者看不到函数内函数参数的赋值,例如如果传递了一个变量,则无法在调用者的范围内模拟对该变量的赋值。但是由于函数可以访问与调用者相同的对象(不进行复制),如果对象是可变的,则对这些对象的突变对调用者来说是可见的,这可能看起来与调用不同按值语义。

        ultrayoshi 的回答中提供了这些 Shared 突变的示例,可以简单解释:当表达式(例如变量访问)评估为对象,并且将所述对象传递给函数时, 没有复制/克隆。

        Call By Value [of the Reference]

        虽然术语“按值调用 [引用]”通常用于描述行为,但应注意 Java/C# 意义上的 JavaScript does not have "references"(或“非引用”值),因此术语具有微妙的误导性 - 至少它不是通过引用调用,它的含义多种多样,而且很多人都理解一个低级的解释。

        在按值调用中,对参数表达式求值,并将结果值绑定到函数中的相应变量。如果函数或过程能够为其参数赋值,则只有其本地副本是赋值——也就是说,当函数返回时,传递给函数调用的[任何变量]在调用者的范围内都不会改变。

        因为只传递了对对象的“引用”(而不是所述对象的副本/克隆),所以语义仅仅是通过共享调用的语义。但是,我在 JavaScript 中避免使用这个术语,因为它引入了不必要的实现细节,并且还引入了实现方式传递对象与原始值的区别。

        描述“call-by-value where the value is a reference”很常见(但不应理解为call-by-reference);另一个术语是共享呼叫。


        因此,当我谈到 JavaScript 中的调用约定时,

        更喜欢使用 Call By Sharing 来讨论行为,我避免 Call By [Value/Reference] 因为它们有太多不同的“含义”并拖入不必要的实现细节。

        【讨论】:

        • 你能澄清你所说的 JavaScript 没有“引用” 是什么意思吗?我很确定var x = "Hello"; 将创建一个 reference 到运行时分配的堆上某处的字符串,x 本身将保存一个 4 字节值(或 64 位 CPU 上的 8 字节) .也许您的意思是 ECMA 规范实际上并没有在任何地方指定这个实现,只有它必须遵循的行为。在这种情况下,我认为可以说几乎所有 JavaScript 引擎都会使用引用来实现这种行为,即使规范不需要它。
        • "JavaScript 没有 Java/C# 意义上的“引用”" JavaScript 准确地 具有 Java/C# 意义上的引用。
        • "JavaScript 的调用约定可以完全在 Call By [Object] 共享语义中讨论。"这实际上只是 按值调用 的另一个名称
        • @user2864740:“C 实现可能使用指针,Java 实现引用”“引用”指向对象的指针。这就是参考的意思。所以你是一样的东西。
        • @user2864740:“引用”(en.wikipedia.org/wiki/Reference_%28computer_science%29) 有一个基本含义,在语义上对应于许多语言所称的“指针”。某些语言可能使用“参考”来表示其他内容是另一个问题。您基本上是在说,在这里谈论“参考”是没有意义的,因为它并没有跨语言一致使用。好的。不管你怎么称呼它,JavaScript 中值的语义 与 Java 中的值完全相同,在 Java 中,非基元被称为“引用”(在 JLS 中定义为指向对象的指针)。
        【解决方案6】:

        数字、字符串等原始值不通过引用传递,仅通过对象传递。例如:

        var myObj = {
          value: "Hello"
        };
        
        function change(localObj) {
          localObj.value = "Bye";
        }
        
        change(myObj);
        console.log(myObj.value) // Prompts "Bye"
        

        【讨论】:

        • 这完全不正确。对对象的引用也是按值传递的。制作了该参考的副本。
        • 好吧@MikeChristensen,这个例子是正确的,但“参考”这个词的使用可能不是。我认为这是一个基本问题,不需要更深入的理论解释:D
        猜你喜欢
        • 1970-01-01
        • 2011-12-07
        • 2012-05-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-12-20
        • 2021-07-03
        • 2019-08-29
        相关资源
        最近更新 更多