【问题标题】:Does JavaScript pass by reference? [duplicate]JavaScript 是否通过引用传递? [复制]
【发布时间】:2021-05-20 14:58:25
【问题描述】:

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

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

是闭包吗?但是没有函数返回。

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 reference pass-by-reference pass-by-value


    【解决方案1】:

    基元通过值传递,对象通过“引用的副本”传递。

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

    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
    

    【讨论】:

    • 虽然不流行,但对象的行为实际上被命名为“共享调用”:en.wikipedia.org/wiki/Call_by_sharing#Call_by_sharing
    • @IoanAlexandruCucu 我个人认为“参考副本”更直观 ;-)
    • @Inverse 我回滚了您的编辑,因为它完全改变了它的语义。对如此高票数的答案做出如此重大的改变也是完全不合适的!
    • 如果您要使用“引用的副本”这个短语,那么您不妨将原语称为“值的副本”。这就是为什么两者实际上都只是“按值传递”。两者都传递值的副本,无论该值是数字、布尔值、字符串还是引用。
    • 这个概念很简单,应该很容易理解。我们很困惑,因为 C++ 及其后代使用了令人困惑和不合逻辑的术语。因此,我们被“参考副本”困住了。但对 Alnitak 表示敬意。
    【解决方案2】:

    这样想:

    每当您在 ECMAscript 中创建 对象 时,该对象都会在一个神秘的 ECMAscript 通用位置中形成,任何人都无法获得。您得到的只是对这个神秘地方的那个对象的引用。

    var obj = { };
    

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

    【讨论】:

    • 而 reference 本身是 按值 传递的,就像 JavaScript 中的其他所有内容一样。
    • @Pointy 参考值是多少?引用是一种类型吗?我觉得这个文字游戏没什么用。
    • @albanx 我意识到这很令人沮丧,但每个专业领域都有专门的术语。 “引用”的含义类似于“指针”在 C 或 C++ 中的含义(C++ 有指针 和 引用)。然而,在 JavaScript 或 Java 等语言中,作为特定对象的“值”只能是对象的 reference。所以它不是一个类型,真的,它是值实际是什么的表征。
    • @albanx 我会将这个意见转达给 2012 年的我 :)
    • @jAndy 在我离开这个世界很久之后,人们就会对我的答案和 cmets 发表评论,这有点病态,这让我感到震惊。
    【解决方案3】:

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

    我写了更长的,more detailed explanation in this link。

    当您传递任何东西(无论是对象还是原始数据)时,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'

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

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

    修改变量影响不同变量的唯一时间是底层对象发生变异时。

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

    【讨论】:

    • “通过复制”和“通过引用”就这么简单,传达了所有相关的含义。我得到的是“它自己的东西”还是“东西”就是你所关心的。
    • 你的作业(没有&),类比似乎只是对传递值的解释,不是吗?那为什么不这么说呢?为什么在谈论按值传递时说按值传递无关紧要
    • 很好的解释雷!
    【解决方案4】:

    函数参数通过值传递或共享传递,但在 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);
    函数范围内的

    重新分配在周围范围内不可见。

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

    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 将采用 call-by-reference 评估策略,则该变量将保存原始引用。这是按共享和按引用之间的关键区别。

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

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

    变异是指修改现有Object 的某些属性。变量绑定到的引用副本以及引用此对象的引用副本保持不变。因此,突变在调用者的范围内是可见的。

    重新赋值的意思是替换绑定到变量的引用副本。由于它只是一个副本,因此持有相同引用副本的其他变量不受影响。因此,重新分配在调用者的范围内不可见,就像使用 call-by-reference 评估策略一样。

    有关 ECMAScript 中 evaluation strategies 的更多信息。

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

      • 这实际上是这里最正确的答案。如果您曾经深入研究过 V8 或竞争引擎,这就是函数调用的实际实现方式。
      • 我打赌对象是指针。对象参数是一个新创建的指针,它指向与传入的指针相同的地址。
      【解决方案6】:

      JavaScript 是按值传递的。

      对于原语,传递原语的值。对于对象,传递对象的引用“值”。

      对象示例:

      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。

      【讨论】:

        【解决方案7】:

        为了创建一个使用 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
        

        【讨论】:

          【解决方案8】:

          实际上,Alnitak is correct 很容易理解,但最终在 JavaScript 中,一切都是按值传递的。

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

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

          【讨论】:

          • 这不是澄清而是混淆。
          • "对象的“值”是什么?它是对象引用。"简单而完美!
          【解决方案9】:

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

          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
          

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

          【讨论】:

            【解决方案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.log 应替换为 alert。

            ★★★

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

            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。

            【讨论】:

              【解决方案11】:

              在人们试图证明这一点的示例中,我看不到 pass-by-reference。我只看到按值传递。

              在变量持有对对象的引用的情况下,引用是那些变量的值,因此引用被传递,然后是按值传递。

              在这样的声明中,

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

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

              【讨论】:

                【解决方案12】:

                对象总是按引用传递,基元按值传递。只需将该参数保持在对象的相同地址即可。

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

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

                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 是通过引用传递函数之后的引用传递

                【讨论】:

                  【解决方案13】:

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

                  function test(value) {
                    console.log('retrieve value');
                    console.log(value());
                  }
                  
                  // call the function like this
                  var value = 1;
                  test(() => value);
                  

                  【讨论】:

                    猜你喜欢
                    • 2010-10-10
                    • 1970-01-01
                    • 2012-07-08
                    • 2013-05-22
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多