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多執行緒程式設計體驗?)。