15

我知道赋值运算符是右结合的。

所以例如 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关键字创建的变量相反。这不是这里的问题。

4

3 回答 3

10

关联性评估顺序之间有一个重要区别。

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

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

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

变量c最初引用a,但赋值的右侧设置cb。哪个属性被分配,a.x或者b.x?答案是因为当仍然引用a.x时,首先评估左侧。ca

通常,表达式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.xfoo在分配给之前进行评估{a:1},因此该x属性被添加到原始{}对象(您可以通过检查来验证bar)。

于 2015-12-01T18:34:36.507 回答
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}

属性访问器分为 setter 和 getter

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

这里已经声明了两个嵌套对象foobar. 在内存中,我们有两个对象AB

[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
于 2015-12-01T17:07:13.957 回答
0

好问题。这里要记住的是,JavaScript 对所有事物都使用指针。很容易忘记这一点,因为在 JavaScript 中无法访问表示内存地址的值(请参阅这个 SO question)。但是意识到这一点对于理解 JavaScript 中的许多东西非常重要。

所以声明

var foo = {};

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

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

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

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

表明如果foobar最初指向同一个对象,bar(仍将指向该原始对象)将看起来像{x: {a: 1}},而foo只是{a: 1}

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

虽然你是对的,作业是右结合的,但你还必须意识到解释器仍然从左到右阅读。让我们举一个深入的例子(抽象出一些位):

var foo = {};

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

foo.x = ....

好的,抓取foo当前指向内存位置 47328 的对象,为其添加一个属性x,然后准备分配x给接下来的内存位置。

foo = ....

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

{a: 1};

好的,在内存中的位置 47452 处创建一个新对象。现在返回链:分配foo指向内存位置 47452。分配内存位置 47328x处对象的属性也指向foo现在指向的内存位置 47452。

简而言之,没有速记方法可以做到

var foo = {a: 1};
foo.x = foo;
于 2015-12-01T18:13:27.543 回答