ES规范解读之自增操作符
原文:https://github.com/kuitos/kuitos.github.io/issues/24
几个月前,不知道什么缘由跟同事讨论了起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:
Evaluate LeftHandSideExpression.
Call GetValue(Result(1)).
Call ToNumber(Result(2)).
Add the value 1 to Result(3), using the same rules as for the + operator (see 11.6.3).
Call PutValue(Result(1), Result(4)).
Return Result(3).
从es上的算法描述,我们能够清晰的得知,后自增操作符是先自增赋值,然后返回自增前的值,这样的一个顺序。
到这里还不算完。
既然i=i++
这种操作最后i还是为原始值,也就是这段代码不会有任何实际意义,那么js引擎有没有可能针对性的做优化,从而避免不必要的自增运算?(如果你用的是IDE,IDE会提示你这是一段无用的代码)
也就是说,我们如何确定,执行引擎一定做了两步操作:
i = i + 1;
return iBeforeIncrease = 0;i = iBeforeIncrease;
还是执行引擎可能会针对性的优化,只做一步操作:
i = iBeforeIncrease;
当我在想怎么去确定这一点时,松波给出了解决方案,用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,有兴趣的同学可以试试用这个方式来验证一下。
顺便抖个机灵,自增操作是非原子性操作,是非线程安全的,多线程环境下共用变量使用自增操作符是会有问题的(前端同学们别急,ES7会为我们带来js多线程编程体验?)。