多重分配混淆

Multiple assignment confusion

提问人:Danield 提问时间:12/2/2015 最后编辑:Danield 更新时间:12/7/2015 访问量:541

问:

我知道赋值运算符是正确的关联。

所以,例如,相当于x = y = z = 2(x = (y = (z = 2)))

在这种情况下,我尝试了以下方法:

foo.x = foo = {a:1}

我期望使用值创建对象,然后创建属性,该属性将只是对对象的引用。foo{a:1}xfoofoo

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

结果实际上是:

ReferenceError:未定义 foo(...)

因此,我尝试了以下方法:

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

现在我不再得到异常,而是未定义!foo.x

为什么作业没有按我预期工作?


免责声明:“重复”问题似乎与我所问的问题有很大不同,因为问题在于在赋值中创建的变量是全局的,与使用关键字创建的变量相同。这不是这里的问题。var

JavaScript 运算符优先级 多重赋

评论

0赞 Blake Yarbrough 12/2/2015
a 是有序的var foo;
0赞 Bhargav Ponnapalli 12/2/2015
问得好。即使在使用 var foo 之后,我也没有在对象 foo 中看到“x”道具。这是怎么发生的?
0赞 Danield 12/2/2015
@PaulRoub 这不是骗局 - 我在问题中添加了免责声明,并简要解释了原因
0赞 Bergi 1/22/2016
相关: 解释 javascript 代码

答:

1赞 tenbits 12/2/2015 #1

编辑了答案以使其简单

首先,您必须了解 Reference- 和 Value- Type 之间的区别。

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}

这里 where 有两个嵌套对象和 .在内存中,我们有两个对象和 .foobarAB

[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
0赞 bowheart 12/2/2015 #2

好问题。这里要记住的是,JavaScript 对所有内容都使用指针。很容易忘记这一点,因为在 JavaScript 中无法访问表示内存地址的值(请参阅此 SO 问题)。但是,为了理解 JavaScript 中的许多内容,实现这一点非常重要。

所以声明

var foo = {};

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

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

该属性实际上是被添加到内存中的原始对象中,同时被分配到指向新对象的指针。例如xfoo{a: 1}

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

显示如果 和 最初指向同一个对象,(它仍然指向该原始对象)将看起来像 ,而 只是 。foobarbar{x: {a: 1}}foo{a: 1}

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

虽然你是对的,因为作业是右联想的,但你也必须意识到口译员仍然从左到右阅读。让我们举一个深入的例子(抽象出一些部分):

var foo = {};

好的,在内存位置 47328(或其他位置)创建一个对象,分配给指向 47328 的指针。foo

foo.x = ....

好的,抓住 foo 当前指向的内存位置 47328 的对象,向其添加一个属性,然后准备分配给接下来要发生的任何内容的内存位置。xx

foo = ....

好的,抓住指针并准备将其分配给接下来要发生的任何内容的内存位置。foo

{a: 1};

好的,在内存中的位置 47452 创建一个新对象。现在回到链上:分配给指向内存位置 47452。将对象的属性分配给内存位置 47328 也指向现在指向的内容 - 内存位置 47452。fooxfoo

总之,没有速记的办法

var foo = {a: 1};
foo.x = foo;
11赞 Michael Liu 12/2/2015 #3

关联性和评估顺序之间存在重要区别。

在 JavaScript 中,即使赋值运算符从右到左分组,在执行实际赋值之前(确实从右到左)也会从左到右计算操作数。请看这个例子:

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

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

该变量最初引用 ,但赋值的右侧设置为 。分配哪个属性,或者?答案是因为首先评估左侧,当仍然引用时。cacba.xb.xa.xca

通常,表达式的计算方法如下:x = y

  1. 评估并记住结果。x
  2. 评估并记住结果。y
  3. 将步骤 2 的结果分配给步骤 1 的结果(并将前者作为表达式的结果返回)。x = y

多个赋值会发生什么情况,如?递归!x = (y = z)

  1. 评估并记住结果。x
  2. 评估并记住结果。为此,请执行以下操作:y = z
    1. 评估并记住结果。y
    2. 评估并记住结果。z
    3. 将步骤 2.2 的结果分配给步骤 2.1 的结果(并将前者作为表达式的结果返回)。y = z
  3. 将步骤 2 的结果分配给步骤 1 的结果(并将前者作为表达式的结果返回)。x = (y = z)

现在让我们看一下您的示例,略微编辑:

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

foo.x在分配给 之前进行计算,因此该属性将添加到原始对象中(您可以通过检查来验证)。foo{a:1}x{}bar

评论

0赞 Danield 12/2/2015
感谢您的精彩解释,但我仍然没有完全理解。我知道左边的表达式喜欢或将首先被计算,但是,为什么在我的示例中,为什么不稍后为它分配函数的返回值 (=1) 或 foo={a:1}....就像你自己在第 3 步中解释的那样?c.xfoo.x
0赞 Michael Liu 12/2/2015
foo.x 确实设置为 的结果。问题是,哪个对象实际上得到了更新——或者?在计算步骤中,用 、 和 代替 。在步骤1中,进行评估;结果是对象的属性。在步骤 2.3 中,设置为 。在步骤 3 中,步骤 2(对象)的结果将分配给步骤 1 的结果(对象的属性)。所以 的最终值是 ,而 的最终值是 。foo = {a:1}{}{a:1}x = (y = z)foo.xxfooy{a:1}zfoo.xx{}foo{a:1}{a:1}x{}foo{a:1}bar{x:{a:1}}
0赞 Danield 12/2/2015
好的,但是为什么不将对象的属性与对象一起分配......不应该创建属性吗?或者换一种说法......为什么有必要?x{}{a:1}xbar
0赞 Michael Liu 12/3/2015
对象的属性确实与对象一起分配。变量不是必需的;我只是出于调试和解释目的而添加它。如果没有 ,就没有办法在设置为对象后引用该对象。通过添加 ,您可以执行 和 查看 和 对象的最终状态。x{}{a:1}barbar{}foo{a:1}barconsole.log(foo)console.log(bar){a:1}{}
1赞 Michael Liu 7/13/2016
@link2pk:不完全是,因为前者创建一个对象,而后者创建两个独立的对象。像这样写后者会更准确:{a: 1}{a: 1}var temp = {a: 1}; foo.x = temp; foo = temp;