JS-连等、赋值表达式、链式赋值 chained assignment 的一些思考

在 ECMAScript(3rd) 文档中有关于赋值表达式的解释:

The production AssignmentExpression:

LeftHandSideExpression = AssignmentExpression
is evaluated as follows:
  1. Evaluate LeftHandSideExpression.
  2. Evaluate AssignmentExpression.
  3. Call GetValue( Result( 2 ) ).
  4. Call PutValue( Result( 1 ), Result( 3 ) ).
  5. Return Result( 3 ).

小弟自己翻译以下:

  1. 左边引用
  2. 右边引用
  3. 右边计算
  4. 赋值
  5. 返回

看上去,好像跟我们从红皮书看的运算顺序不一样,链式赋值都是从右往左的啊,怎么会是左边引用先呢?

根据这个,有比较经典的以下题目,原题:

var a = { n: 1 }, b = a; // ①
a.x = a = { n: 2 };      // ②
console.log(a.x);        // undefined
console.log(b.x);        // { n: 2 }

对以上标记的语句进行分析:

  1. 声明 a 和 b ,分配新的堆 { n: 1 },a 和 b 都赋值为指向 { n: 1 } 的地址,栈中 a 和 b 是不同的,但指向同一个堆 { n: 1 }

  1. 点运算比赋值运算优先级高,所以 a.x 会先运算,栈中找到 a,指向堆 { n: 1 },找不到 a.x,所以 js 为堆内存的对象加了个新成员 x,赋予初始值 undefined。

这也是为啥引用未定义的变量会报错,而引用一个对象的不存在的属性会返回 undefined。

(右边第一个等号开始)

  1. 【左边引用】a 此时是指向 { n: 1 } 的这个堆
  2. 【右边引用】分配了新的堆内存,存放 { n: 2 }
  3. 【右边计算】{ n: 2 }
  4. 【赋值】a = { n: 2 },此时这个 a 已经由指向 (1) 那个堆,改为了 (3) 的新堆了,而此时 b 还是指向 (1) 的堆,即 a = { n: 2 },b = { n: 1 }
  5. 【返回】{ n: 2 }

(右起第二个等号开始)

  1. 【左边引用】a.x 为 undefined,注意(1) 比 (5) 先运算,所以这个 a 啊,已经引用过了,他不会再因为 (5) 改变,a 其实是 b,即指向 { n: 1 }

增强理解:变量 a 在引用一次以后,他就不再变化了——点运算 a.x 让 a 引用了。而在(5)这个 a 赋值了新堆,那么 a 指了 { n: 2 },而 b 的引用还存在,所以 { n: 1 } 没被回收,a.x 也即 b.x

  1. 【右边引用】这里就是其右边等号的返回值,即(8)的 { n: 2 }
  2. 【右边计算】{ n: 2 }
  3. 【赋值】a.x = { n: 2 },其实就是 b 的变量赋值!
  4. 【返回】{ n: 2 }

结果如下:

console.log(a) // { n: 2 }
console.log(b) // { n: 1, x: { n: 2 } }

在so网站上找到一段代码,我们可以修改一下 a 的get/set,看看 ② 到底执行的顺序是啥:

var _a;
var logging = false;
Object.defineProperty(window, 'a', {
    get: function () {
        if (logging) {
            console.log('getting a');
        }
        return _a;
    },
    set: function (v) {
        if (logging) {
            console.log('setting a');
        }
        _a = v;
    }
});

a = { n: 1 };
var b = a;

logging = true;
a.x = a = { n: 2 };
logging = false;

console.log(a);
console.log(b);

可以看控制台的输出,是先运算了 getting 再运算 setting,而且只运算了一遍。也就是说,a 只是被引用了一遍,而且还是在赋值以前,那就是 a.x。

getting a
setting a
{ n: 2 }
{ n: 1, x: { n: 2 } }

结尾:其实有几个点还是没弄清楚

  1. 右边的引用和计算到底分别干了啥?以上没分析到答案
  2. 变量未声明直接使用会报错,对象的未声明属性却不会
console.log(hah)    // Uncaught ReferenceError: hah is not defined
var o = { o: 'o' }
console.log(o.hah)  // undefined 
console.log(o)      // { o: 'o' } 这个也没看出来有给初始值呢,也许,是在引用到未声明的属性时才会分配初始值,没有引用到就不会分配,直接将原对象引用出来

参考文章
1. What's the result of this JavaScript code snippet, and why?
2. JS:连续赋值运算

你可能感兴趣的:(JS-连等、赋值表达式、链式赋值 chained assignment 的一些思考)