多重赋值混淆
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}
这里定义了两个嵌套对象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 中表示内存地址的值(参见 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 = {};
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.x
或 b.x
?答案是 a.x
因为左边先求值,当 c
仍然引用 a
.
一般来说,表达式x = y
的计算方式如下:
- 计算
x
并记住结果。
- 计算
y
并记住结果。
- 将步骤 2 的结果赋给步骤 1 的结果(并且 return 前者作为表达式
x = y
的结果)。
如 x = (y = z)
中的多重赋值会怎样?递归!
- 计算
x
并记住结果。
- 评估
y = z
并记住结果。去做这个:
- 计算
y
并记住结果。
- 计算
z
并记住结果。
- 将步骤 2.2 的结果分配给步骤 2.1 的结果(并且 return 前者作为表达式
y = z
的结果)。
- 将步骤 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.x
在 foo
赋值给 {a:1}
之前计算,因此 x
属性 被添加到原始 {}
对象(您可以通过检查 bar
来验证)。
我知道赋值运算符是右结合的。
所以例如 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}
这里定义了两个嵌套对象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 中表示内存地址的值(参见 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 = {};
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 propertyx
to it, and get ready to assignx
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 propertyx
of the object at memory location 47328 to also point to whatfoo
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.x
或 b.x
?答案是 a.x
因为左边先求值,当 c
仍然引用 a
.
一般来说,表达式x = y
的计算方式如下:
- 计算
x
并记住结果。 - 计算
y
并记住结果。 - 将步骤 2 的结果赋给步骤 1 的结果(并且 return 前者作为表达式
x = y
的结果)。
如 x = (y = z)
中的多重赋值会怎样?递归!
- 计算
x
并记住结果。 - 评估
y = z
并记住结果。去做这个:- 计算
y
并记住结果。 - 计算
z
并记住结果。 - 将步骤 2.2 的结果分配给步骤 2.1 的结果(并且 return 前者作为表达式
y = z
的结果)。
- 计算
- 将步骤 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.x
在 foo
赋值给 {a:1}
之前计算,因此 x
属性 被添加到原始 {}
对象(您可以通过检查 bar
来验证)。