【问题标题】:Scope in javascript acting weirdjavascript中的作用域表现得很奇怪
【发布时间】:2015-09-04 23:44:24
【问题描述】:

对象在 javascript 中与它们的引用一起传递。从任何地方都应该反映该对象的含义变化。 在这种情况下,console.log(a) 的预期输出是 {}

function change(a,b) {
    a.x = 'added';
    a = b;//assigning a as {} to b
}
a={}
b={}
change(a,b);
console.log(a); //expected {} but output {x:'added'}
console.log(b)

这里发生了什么?据我所知,这不应该是因为功能范围。 谢谢

【问题讨论】:

  • 对象引用作为功能参数传递。因此,无论您在函数内部进行什么更改,它都会影响实际的对象。
  • @VigneswaranMarimuthu 所以输出应该是 {x:'added'} 对吧?但这里不是这样
  • 在函数内部将b 分配给a 无效,因为函数范围内的a 与全局范围内的a 是不同的变量。
  • 我喜欢你说你知道对象是通过引用传递的,然后对对象通过引用传递表示非常震惊和惊讶。
  • 这只是令人困惑,因为函数参数中的变量使用与全局变量相同的名称。如果函数参数名称是foobar,可能更容易理解它为什么会这样工作。

标签: javascript function scope pass-by-reference


【解决方案1】:

如果您添加了另一行,您可以更清楚地了解正在发生的事情:

function change(a,b) {
    a.x = 'added';
    a = b;
    a.x = 'added as well';
};
a={};
b={};
change(a,b);
console.log(a);  //{x:'added'}
console.log(b);  //{x:'added as well'}

当您执行a = b 时,您将局部变量a 分配给b 持有的引用。

【讨论】:

  • 是的,谢谢你这个带有尼古拉回答的例子实际上解决了我的问题。
  • 这真是个令人费解的人。几行代码很好的解释!
【解决方案2】:

您是对的,对象是通过引用传递的,并且对函数中对象所做的任何更改都会在各处反映出来。这正是为什么在函数中添加x 属性会修改其外部的对象。

你缺少的是a = b;这一行不修改对象,它修改了对对象的引用。如果需要设置引用,可以在另一个容器对象/数组中传递这两个对象:

function change(container) {
    container.a.x = 'added';
    container.a = container.b;//assigning a as {} to b
}
var container = { a: {}, b: {}};
change(container);
console.log(container.a);
console.log(container.b)

【讨论】:

  • 我觉得这个需要太多动作。看看我的answer
  • 但是对象不是通过引用传递的! a = b 只修改 local 引用(变量)!
  • “你说得对,对象是通过引用传递的”——不,它们不是。 JavaScript 是严格按值传递的,OP 的代码很好地证明了这一事实。如果 JavaScript 是通过引用传递的,那么 OP 的代码就可以工作,而且他一开始就不会问这个问题。
  • 我在 Java 世界中熟悉的术语,我认为也适用于此,是 pass-by-copy-of-reference。
  • @Bergi:那个链接就是答案 :) 它准确地解释了 OP 的问题。
【解决方案3】:

函数上下文中的变量“a”与函数外部的变量“a”不同。此代码在语义上与您的代码等效:

function change(foo,bar) {
    foo.x = 'added';
    foo = bar;//assigning foo as {} to bar
}
a={}
b={}
change(a,b);
console.log(a); //expected {} but output {x:'added'}
console.log(b)

在这种情况下,很明显'foo'变量只存在于函数内部,并且foo = bar不会改变a,因为引用是按值传递的。

【讨论】:

  • 这个明白了。 function change(a,b) 创建局部变量 ab,因此赋值 a = b 分配给局部 a 变量,而不是全局变量。跨度>
  • 换一种说法:foo 确实持有对a 也引用的对象的引用。 foo 不是对 a 的引用。
【解决方案4】:

对象在 javascript 中与它们的引用一起传递。

不,他们不是。 ECMAScript/JavaScript 是严格按值传递的。 (更准确地说,call-by-sharing,这是 pass-by-value 的一种特殊情况。)

这里发生了什么?

这只是普通的按值传递。

您的困惑源于您错误地认为 ECMAScript/JavaScript 是通过引用传递的,而实际上并非如此。

ECMAScript 使用按值传递,或者更准确地说,是按值传递的一种特殊情况,其中传递的值始终 是一个指针。这种特殊情况有时也称为 call-by-sharing、call-by-object-sharing 或 call-by-object。

Java(用于对象)、C#(默认情况下用于引用类型)、Smalltalk、Python、Ruby 以及几乎所有创建的面向对象语言都使用相同的约定。

注意:某些类型(例如Numbers)实际上是通过值直接传递的,而不是通过中间指针传递的。但是,由于它们是不可变的,在这种情况下,按值传递和按对象共享之间没有可观察到的行为差异,因此您可以通过简单地处理所有内容来大大简化您的心智模型作为按对象共享调用。只需将这些特殊情况解释为您无需担心的内部编译器优化即可。

这是一个简单的示例,您可以运行它来确定 ECMAScript(或任何其他语言,在您翻译后)的参数传递约定:

function isEcmascriptPassByValue(foo) {
  foo.push('More precisely, it is call-by-object-sharing!');
  foo = 'No, ECMAScript is pass-by-reference.';
  return;
}

var bar = ['Yes, of course, ECMAScript *is* pass-by-value!'];

isEcmascriptPassByValue(bar);

console.log(bar);
// Yes, of course, ECMAScript *is* pass-by-value!,
// More precisely, it is call-by-object-sharing!

如果你熟悉 C#,这是了解值类型和引用类型的值传递和引用传递之间的区别的一个很好的方法,因为 C# 支持所有 4 种组合:值类型的值(“传统的值传递”),引用类型的值传递(共享调用、对象调用、对象共享调用,如 ECMAScript 中)、传递-引用类型的按引用,值类型的按引用。

(实际上,即使您了解 C#,这也不难。)

// In C#, struct defines a value type, class defines a reference type
struct MutableCell
{
    public string value;
}

class Program
{
    // the ref keyword means pass-by-reference, otherwise it's pass-by-value
    // You must explicitly request pass-by-reference both at the definition and the call
    static void IsCSharpPassByValue(string[] foo, MutableCell bar, ref string baz, ref MutableCell qux)
    {
        foo[0] = "More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.";
        foo = new string[] { "C# is not pass-by-reference." };

        bar.value = "For value types, it is *not* call-by-sharing.";
        bar = new MutableCell { value = "And also not pass-by-reference." };

        baz = "It also supports pass-by-reference if explicitly requested.";

        qux = new MutableCell { value = "Pass-by-reference is supported for value types as well." };
    }

    static void Main(string[] args)
    {
        var quux = new string[] { "Yes, of course, C# *is* pass-by-value!" };

        var corge = new MutableCell { value = "For value types it is pure pass-by-value." };

        var grault = "This string will vanish because of pass-by-reference.";

        var garply = new MutableCell { value = "This string will vanish because of pass-by-reference." };

        // the first two are passed by value, the other two by reference
        IsCSharpPassByValue(quux, corge, ref grault, ref garply);

        Console.WriteLine(quux[0]);
        // More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.

        Console.WriteLine(corge.value);
        // For value types it is pure pass-by-value.

        Console.WriteLine(grault);
        // It also supports pass-by-reference if explicitly requested.

        Console.WriteLine(garply.value);
        // Pass-by-reference is supported for value types as well.
    }
}

【讨论】:

    【解决方案5】:

    好的,您已经发现 JavaScript 对象具有引用语义,因此修改引用对象会对原始范围内的同一对象产生影响。

    您还需要意识到= 不是这些规则的一部分;它不仅会执行赋值,还会将引用重新绑定到新对象。

    在幕后,可以这么说,这基本上就是您的原始参考资料的形成方式。

    【讨论】:

    • 对象具有引用语义(即,您始终传递指针,而不是对象本身),并且参数传递始终是按值传递而不是按引用传递。 (后者是 OP 缺少的关键部分,他似乎认为 JavaScript 是通过引用传递的,这是不正确的。)
    • @JörgWMittag:可以说 JavaScript 是通过引用传递的,也可以说不是。该术语在整个社区中的使用并不一致,您不能断然地说一个是“不正确的”,而另一个是。你可以说的是 JavaScript 使用了pass by sharing,这是唯一可以在这里应用的精确、准确和客观的术语。其余的只是个人偏好,在不精确时选择什么术语。
    • Pass-by-sharing 是 pass-by-value 的一种特殊情况。 OP 认为 JavaScript 是按引用传递的,他在假设 JavaScript 是按引用传递的前提下编写代码-参考。我知道你不想信任互联网上的随机陌生人,但你信任 JavaScript 吗?然后你可以自己问:……
    • function isEcmascriptPassByValue(foo) { foo = 'No, ECMAScript is pass-by-reference.'; return; }; var bar = 'Yes, of course, ECMAScript *is* pass-by-value!'; isEcmascriptPassByValue(bar); alert(bar); // Yes, of course, ECMAScript *is* pass-by-value!
    • @JörgWMittag:这几乎就像你没有读过我说的任何东西。您正在应用您自己对术语的狭义定义并使用它来做出具体的事实陈述,但您不能这样做:两者的普遍接受的含义并没有充分涵盖 JavaScript 的语义 .阅读我链接到您的文章。
    【解决方案6】:

    这应该有助于解决您的问题:

    var obj = {}, anotherObj = {};
    // in case if they are not global, make them global or define parent scope to be able to modify inside the function
    window.obj = obj;
    window.anotherObj = anotherObj;
    function swap(a, b) {
      window[a].newProp = 'XYZ';
      window[a] = window[b]; // now obj is gone, because replaced with anotherObj
    }
    swap('obj','anotherObj');
    console.log(obj); // now it would give Object {}
    

    【讨论】:

    • 我看不出您的回答与问题有何关联。而且您的代码显示的只是对象和数组也不是不是通过引用传递的(如果是,您就不需要那个“范围”)。
    • @Bergi 这个想法和尼古拉的回答是一样的。唯一的区别是容器是window 对象和变量名称作为字符串传递给函数。如果 OP 想要解决他的问题,而不仅仅是解释正在发生的事情,那么这正是他所需要的。
    • "对象和数组默认通过引用传递。" – 不,他们不是。 JavaScript 是严格按值传递的,OP 的代码很好地证明了这一事实。如果 JavaScript 是通过引用传递的,那么他的 OP 代码就可以工作,而且他一开始就不会问这个问题。
    • @JörgWMittag 争论如何理解这句话毫无意义。我已经与Bergi进行了更高的辩论并表达了我的意见。关键是该解决方案适合 OP 的需求。
    猜你喜欢
    • 2022-01-04
    • 2013-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-12
    • 2012-11-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多