我理解赋值运算符是右关联的。
因此,例如x = y = z = 2
相当于(x = (y = (z = 2)))
在这种情况下,我尝试了以下内容:
foo.x = foo = {a:1}
我预计将使用值foo
创建对象{a:1}
,然后将在x
上创建属性foo
,这只是对{的引用{1}}对象。
(实际上,如果我要将多个赋值语句分成两个单独的语句foo
),会发生这种情况。
结果实际上是:
ReferenceError:foo未定义(...)
然后我尝试了以下内容:
foo = {a:1};foo.x = foo;
现在我不再获得例外,但var foo = {};
foo.x = foo = {a:1};
未定义!
为什么作业没有按照我的预期运作?
<小时/> 免责声明:&#39;重复&#39;问题似乎与我提出的问题非常不同,因为问题在于赋值中创建的变量是全局的,与使用foo.x
关键字创建的变量相关联。这不是问题所在。
答案 0 :(得分:10)
关联性与评估顺序之间存在重要差异。
在JavaScript中,即使赋值运算符分组从右到左,操作数也会在执行实际赋值之前从左到右进行评估( do < / em>从右到左发生)。考虑这个例子:
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
并记住结果。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
)。
答案 1 :(得分:1)
编辑答案以简化
首先,您必须了解 参考 - 和 值 - 类型之间的差异。
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}
Property Accessor分为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
答案 2 :(得分:0)
好问题。这里要记住的是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};
表示如果foo
和bar
最初指向同一个对象,bar
(仍将指向该原始对象)将显示为{x: {a: 1}}
,而foo
只是{a: 1}
。
那么为什么foo
看起来不像{a: 1, x: foo}
?
虽然你是正确的,因为作业是正确的关联,你还必须意识到解释器仍然从左到右阅读。让我们来看一个深入的例子(抽象出一些部分):
var foo = {};
好的,在内存位置47328(或其他)中创建一个对象,将
foo
指定给指向47328的指针。
foo.x = ....
好的,在内存位置47328抓取
foo
当前指向的对象,向其添加属性x
,然后准备好分配x
到了下一步的记忆位置。
foo = ....
好的,抓住指针
foo
并准备好将其分配到接下来会发生的任何内存位置。
{a: 1};
好的,在位置47452的内存中创建一个新对象。现在返回链:分配
foo
指向内存位置47452.将内存位置47328的对象的属性x
分配给还指出foo
现在指向的内存位置47452。
简而言之,没有简便的方法
var foo = {a: 1};
foo.x = foo;