提问人:Danield 提问时间:12/2/2015 最后编辑:Danield 更新时间:12/7/2015 访问量:541
多重分配混淆
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(...)
因此,我尝试了以下方法:
var foo = {};
foo.x = foo = {a:1};
现在我不再得到异常,而是未定义!foo.x
为什么作业没有按我预期工作?
免责声明:“重复”问题似乎与我所问的问题有很大不同,因为问题在于在赋值中创建的变量是全局的,与使用关键字创建的变量相同。这不是这里的问题。
var
答:
编辑了答案以使其简单
首先,您必须了解 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 有两个嵌套对象和 .在内存中,我们有两个对象和 .foo
bar
A
B
[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 中无法访问表示内存地址的值(请参阅此 SO 问题)。但是,为了理解 JavaScript 中的许多内容,实现这一点非常重要。
所以声明
var foo = {};
在内存中创建一个对象,并将指向该对象的指针分配给 。现在,当此语句运行时:foo
foo.x = foo = {a: 1};
该属性实际上是被添加到内存中的原始对象中,同时被分配到指向新对象的指针。例如x
foo
{a: 1}
var foo, bar = foo = {};
foo.x = foo = {a: 1};
显示如果 和 最初指向同一个对象,(它仍然指向该原始对象)将看起来像 ,而 只是 。foo
bar
bar
{x: {a: 1}}
foo
{a: 1}
那么为什么foo
看起来不像{a: 1, x: foo}
呢?
虽然你是对的,因为作业是右联想的,但你也必须意识到口译员仍然从左到右阅读。让我们举一个深入的例子(抽象出一些部分):
var foo = {};
好的,在内存位置 47328(或其他位置)创建一个对象,分配给指向 47328 的指针。
foo
foo.x = ....
好的,抓住
foo
当前指向的内存位置 47328 的对象,向其添加一个属性,然后准备分配给接下来要发生的任何内容的内存位置。x
x
foo = ....
好的,抓住指针并准备将其分配给接下来要发生的任何内容的内存位置。
foo
{a: 1};
好的,在内存中的位置 47452 创建一个新对象。现在回到链上:分配给指向内存位置 47452。将对象的属性分配给内存位置 47328 也指向现在指向的内容 - 内存位置 47452。
foo
x
foo
总之,没有速记的办法
var foo = {a: 1};
foo.x = foo;
关联性和评估顺序之间存在重要区别。
在 JavaScript 中,即使赋值运算符从右到左分组,在执行实际赋值之前(确实从右到左)也会从左到右计算操作数。请看这个例子:
var a = {};
var b = {};
var c = a;
c.x = (function() { c = b; return 1; })();
该变量最初引用 ,但赋值的右侧设置为 。分配哪个属性,或者?答案是因为首先评估左侧,当仍然引用时。c
a
c
b
a.x
b.x
a.x
c
a
通常,表达式的计算方法如下:x = y
- 评估并记住结果。
x
- 评估并记住结果。
y
- 将步骤 2 的结果分配给步骤 1 的结果(并将前者作为表达式的结果返回)。
x = y
多个赋值会发生什么情况,如?递归!x = (y = z)
- 评估并记住结果。
x
- 评估并记住结果。为此,请执行以下操作:
y = z
- 评估并记住结果。
y
- 评估并记住结果。
z
- 将步骤 2.2 的结果分配给步骤 2.1 的结果(并将前者作为表达式的结果返回)。
y = z
- 评估并记住结果。
- 将步骤 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
评论
c.x
foo.x
foo.x
确实设置为 的结果。问题是,哪个对象实际上得到了更新——或者?在计算步骤中,用 、 和 代替 。在步骤1中,进行评估;结果是对象的属性。在步骤 2.3 中,设置为 。在步骤 3 中,步骤 2(对象)的结果将分配给步骤 1 的结果(对象的属性)。所以 的最终值是 ,而 的最终值是 。foo = {a:1}
{}
{a:1}
x = (y = z)
foo.x
x
foo
y
{a:1}
z
foo.x
x
{}
foo
{a:1}
{a:1}
x
{}
foo
{a:1}
bar
{x:{a:1}}
x
{}
{a:1}
x
bar
x
{}
{a:1}
bar
bar
{}
foo
{a:1}
bar
console.log(foo)
console.log(bar)
{a:1}
{}
{a: 1}
{a: 1}
var temp = {a: 1}; foo.x = temp; foo = temp;
评论
var foo;