ES规范解读之自增操作符

@kuitos 2015-08-31 15:21:24发表于 kuitos/kuitos.github.io ecmascriptjavascript

ES规范解读之自增操作符

几个月前,不知道什么缘由跟xx同学讨论了起js里自增操作符(i++)的问题,现将前因后果整理出来,传于世人😂

事情起源于这样一段代码

var i = 0;
i = i++;
console.log(i);

来,都来说说答案是啥?
结果是0
换一种形式,或许大家不会有多少疑问

var i = 0;
var a = i++;
console.log(a); // 0

没错,这也是我们初学自增操作符的经典例子,对这结果还有疑问请自觉面壁。。。
遥想当年学习自增操作符的口诀大致是,i++ 是先用后自增,++i 是先自增再用
那么按照这个思路,上面的代码解析流程应该是这样的

var i =0;
i = i;
i = i + 1;

可惜结果并不是这样的
按照犀牛书上的描述,后增量(post increment)操作符的特点是

它对操作数进行增量计算,但返回未作增量计算的(unincremented)值。

但是书上并没有告诉我们,先做增量计算再返回之前的值,还是返回之前的值再做增量计算。
对于这种疑问,我们只能求助ecmascript给出官方解释:

Postfix Increment Operator(后自增操作符)

The production PostfixExpression : LeftHandSideExpression [no LineTerminator here] ++ is evaluated as follows:

  1. Evaluate LeftHandSideExpression.
  2. Call GetValue(Result(1)).
  3. Call ToNumber(Result(2)).
  4. Add the value 1 to Result(3), using the same rules as for the + operator (see 11.6.3).
  5. Call PutValue(Result(1), Result(4)).
  6. Return Result(3).

从es上的算法描述,我们能够清晰的得知,后自增操作符是先自增赋值,然后返回自增前的值,这样的一个顺序。
到这里还不算完。
既然i=i++这种操作最后i还是为原始值,也就是这段代码不会有任何实际意义,那么js引擎有没有可能针对性的做优化,从而避免不必要的自增运算?(如果你用的是IDE,IDE会提示你这是一段无用的代码)
也就是说,我们如何确定,执行引擎一定做了两步操作:

  1. i = i + 1; return iBeforeIncrease = 0;
  2. i = iBeforeIncrease;

还是执行引擎可能会针对性的优化,只做一步操作:

  1. i = iBeforeIncrease;

当我在想怎么去确定这一点时,xx给出了解决方案,用Object.observe()方法啊!!(该方法是ES7提案中的新api,不过chrome早早的实现了)

var obj = {i:0};
Object.observe(obj, function(changes){
    console.log(changes);
});
obj.i = obj.i++;

代码放到chrome中跑一下,可以看到,改变触发了两次,也就是i做了两次修改操作
另外firefox中也提供了一个类似的api,Object.prototype.watch,有兴趣的同学可以试试用这个方式来验证一下。

顺便抖个机灵,自增操作是非原子性操作,是非线程安全的,多线程环境下共用变量使用自增操作符是会有问题的。