多项任务混淆

时间:2015-12-01 16:53:57

标签: javascript operator-precedence associativity multiple-assignment

我理解赋值运算符是右关联的。

因此,例如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关键字创建的变量相关联。这不是问题所在。

3 个答案:

答案 0 :(得分:10)

关联性评估顺序之间存在重要差异。

在JavaScript中,即使赋值运算符分组从右到左,操作数也会在执行实际赋值之前从左到右进行评估 do < / em>从右到左发生)。考虑这个例子:

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

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

变量c最初引用a,但分配的右侧将c设置为b。分配了哪个属性,a.xb.x?答案为a.x,因为当c仍然引用a时,会先评估左侧。

通常,表达式x = y的计算方法如下:

  1. 评估x并记住结果。
  2. 评估y并记住结果。
  3. 将步骤2的结果分配给步骤1的结果(并将前者作为表达式x = y的结果返回)。
  4. 多次分配会发生什么情况,如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)的结果返回)。
    4. 现在让我们看看你的例子,稍加编辑:

      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}

这里已经删除了两个嵌套对象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

答案 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};

表示如果foobar最初指向同一个对象,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;