Javascript 多重赋值混淆
我知道赋值运算符是右关联的Javascript 多重赋值混淆,javascript,operator-precedence,associativity,multiple-assignment,Javascript,Operator Precedence,Associativity,Multiple Assignment,我知道赋值运算符是右关联的 A['x'] = foo = {a:1} 例如,x=y=z=2相当于(x=(y=(z=2)) 在这种情况下,我尝试了以下方法: foo.x = foo = {a:1} var foo = {}; foo.x = foo = {a:1}; 我希望对象foo将使用值{a:1}创建,然后在foo上创建属性x,该属性将只是对foo对象的引用 (如果我将多重赋值语句分成两个单独的语句,就会出现这种情况foo={a:1};foo.x=foo;) 结果实际上是: Refere
A['x'] = foo = {a:1}
例如,x=y=z=2
相当于(x=(y=(z=2))
在这种情况下,我尝试了以下方法:
foo.x = foo = {a:1}
var foo = {};
foo.x = foo = {a:1};
我希望对象foo
将使用值{a:1}
创建,然后在foo
上创建属性x
,该属性将只是对foo
对象的引用
(如果我将多重赋值语句分成两个单独的语句,就会出现这种情况foo={a:1};foo.x=foo;
)
结果实际上是:
ReferenceError:未定义foo(…)
因此,我尝试了以下方法:
foo.x = foo = {a:1}
var foo = {};
foo.x = foo = {a:1};
现在我不再得到异常,但是foo.x
是未定义的
为什么作业没有按我预期的那样进行?
免责声明:“重复”问题似乎与我提出的问题大不相同,因为问题在于分配中创建的变量是全局变量,与使用
var
关键字创建的变量相同。这不是这里的问题。编辑了答案以使其简单化
首先,您必须了解参考-和值-类型之间的区别
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}
这里有两个嵌套对象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中无法访问表示内存地址的值(请参阅)。但是要理解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}
虽然您正确地认识到赋值是右关联的,但您还必须意识到解释器仍然从左向右读取。让我们举一个深入的例子(抽象出一些位):
好的,在内存位置47328(或其他位置)中创建一个对象,将foo
分配给指向47328的指针
好的,抓取foo
当前指向内存位置47328的对象,向其添加一个属性x
,然后准备将x
分配给下一个对象的内存位置
好的,抓取指针foo
,准备将其分配到下一步的内存位置
好的,在47452位置的内存中创建一个新对象。现在回到链的上游:分配foo
指向内存位置47452。将内存位置47328处对象的属性x
指定给foo
现在指向的对象--内存位置47452
简言之,没有速记方法
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
并记住结果x=y
的结果返回)x=(y=z)
?重现
x
并记住结果y
并记住结果z
并记住结果y=z
的结果返回)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
进行验证)。avar fo
var a = {};
var b = {};
var c = a;
c.x = (function() { c = b; return 1; })();
var foo = {};
var bar = foo; // save a reference to foo
foo.x = (foo = {a:1}); // add parentheses for clarity