多重赋值混淆

Multiple assignment confusion

我知道赋值运算符是右结合的。

所以例如 x = y = z = 2 等同于 (x = (y = (z = 2)))

既然如此,我尝试了以下方法:

foo.x = foo = {a:1}

我预计对象 foo 将使用值 {a:1} 创建,然后 属性 x 将在 foo 上创建,这将只是是对 foo 对象的引用。

(如果我将多重赋值语句分成两个单独的语句,实际上会发生这种情况 foo = {a:1};foo.x = foo;

结果实际上是:

ReferenceError: foo is not defined(…)

然后我尝试了以下方法:

var foo = {};
foo.x = foo = {a:1};

现在我不再遇到异常,但 foo.x 未定义!

为什么作业没有像我预期的那样工作?


免责声明:'duplicate' 问题似乎与我要问的问题非常不同,因为问题是在赋值中创建的变量是全局的,与使用 var 关键字创建的变量相对。这不是这里的问题。

编辑了答案使其变得简单

首先你要明白Reference-Value-之间的区别类型。

var foo = {};

foo 变量保存对内存中对象的引用,比方说 A

现在,访问器有两种艺术:变量访问器和属性访问器。

所以foo.x = foo = {a:1}可以理解为

[foo_VARIABLE_ACCESSOR][x_PROPERTY_ACCESSOR] = [foo_VARIABLE_ACCESSOR] = {a:1}

!!! 首先评估访问器链以获得最后一个访问器,然后对其进行关联评估。

A['x'] = foo = {a:1}

属性 访问器分为 setter 和 getter

var foo = { bar: {} };
foo.bar.x = foo = {a:1}

这里定义了两个嵌套对象foobar。在内存中我们有两个对象 AB.

[foo_VAR_ACCESSOR][bar_PROP_GETTER][x_PROP_ACCESSOR] = [foo_VAR_ACCESSOR] = {a:1}

> A[bar_PROP_GETTER][x_PROP_ACCESSOR] = [foo_VAR_ACCESSOR] = {a:1}
> B[x_PROP_ACCESSOR] = [foo_VAR_ACCESSOR] = {a:1}
> B['x'] = foo = {a: 1}

这里有小例子

var A = {};
var B = {}
Object.defineProperty(A, 'bar', {
    get () {
        console.log('A.bar::getter')
        return B;
    }
})
Object.defineProperty(B, 'x', {
    set () {
        console.log('B.x::getter')
    }
});

var foo = A;
foo.bar.x = foo = (console.log('test'), 'hello');

// > A.bar.getter
// > test
// > B.x.setter

好问题。这里要记住的是 JavaScript 对所有内容都使用指针。很容易忘记这一点,因为不可能访问 JavaScript 中表示内存地址的值(参见 this SO question)。但是,为了理解 JavaScript.

中的许多内容,意识到这一点非常重要

所以声明

var foo = {};

在内存中创建一个对象并将指向该对象的指针分配给 foo。现在当这条语句运行时:

foo.x = foo = {a: 1};

属性 x 实际上被添加到内存中的原始对象,而 foo 被分配一个指向新对象 {a: 1} 的指针。例如,

var foo, bar = foo = {};
foo.x = foo = {a: 1};

表明如果 foobar 最初指向同一个对象,bar(仍将指向原始对象)看起来像 {x: {a: 1}} , 而 foo 只是 {a: 1}.

那为什么 foo 看起来不像 {a: 1, x: foo}

虽然你说赋值是右结合的是对的,但你也必须意识到解释器仍然是从左到右阅读的。让我们来看一个深入的例子(抽象出一些位):

var foo = {};

Okay, create an object in memory location 47328 (or whatever), assign foo to a pointer that points to 47328.

foo.x = ....

Okay, grab the object that foo currently points to at memory location 47328, add a property x to it, and get ready to assign x to the memory location of whatever's coming next.

foo = ....

Okay, grab the pointer foo and get ready to assign it to the memory location of whatever's coming next.

{a: 1};

Okay, create a new object in memory at location 47452. Now go back up the chain: Assign foo to point to memory location 47452. Assign property x of the object at memory location 47328 to also point to what foo now points to--memory location 47452.

总之,没有shorthand办法

var foo = {a: 1};
foo.x = foo;

关联性求值顺序 之间存在重要区别。

在JavaScript中,即使赋值运算符groups从右到左,操作数也是evaluated之前从左到右执行实际分配(do 从右到左发生)。考虑这个例子:

var a = {};
var b = {};
var c = a;

c.x = (function() { c = b; return 1; })();

变量 c 最初引用 a,但赋值的右侧将 c 设置为 b。哪个 属性 被分配,a.xb.x?答案是 a.x 因为左边先求值,当 c 仍然引用 a.

一般来说,表达式x = y的计算方式如下:

  1. 计算 x 并记住结果。
  2. 计算 y 并记住结果。
  3. 将步骤 2 的结果赋给步骤 1 的结果(并且 return 前者作为表达式 x = y 的结果)。

x = (y = z) 中的多重赋值会怎样?递归!

  1. 计算 x 并记住结果。
  2. 评估y = z并记住结果。去做这个:
    1. 计算 y 并记住结果。
    2. 计算 z 并记住结果。
    3. 将步骤 2.2 的结果分配给步骤 2.1 的结果(并且 return 前者作为表达式 y = z 的结果)。
  3. 将步骤 2 的结果分配给步骤 1 的结果(并且 return 前者作为表达式 x = (y = z) 的结果)。

现在让我们看看您的示例,稍作编辑:

var foo = {};
var bar = foo;         // save a reference to foo
foo.x = (foo = {a:1}); // add parentheses for clarity

foo.xfoo 赋值给 {a:1} 之前计算,因此 x 属性 被添加到原始 {} 对象(您可以通过检查 bar 来验证)。