晚上在讀 lodash 源碼的時候虱而,看到 baseSlice 中有這樣一行代碼:
start >>>= 0;
這不就是無符號右移嘛,當時第一感覺是是為了取絕對值,后來發(fā)現(xiàn)并不是漱受,嘗試了多次之后伶唯,發(fā)現(xiàn)情況有點詭異啊,我們使用 chrome 調(diào)試工具運行一下 js 中的無符號右移 0 位世杀。
不僅是 null 無符號右移會變成 0 阀参,js 中的其他非數(shù)值做此運算都會變成 0 。
接下來我們來看看為什么會這樣(事實上不僅僅只是無符號右移是這樣)瞻坝。要理解這個問題需要先明白什么是位運算以及為什么需要位運算蛛壳,然后搞明白 js 中的位運算有什么特別之處。
什么是位運算?
敬請期待
js 中的位運算有什么特別之處呢炕吸?
(這一部分我是拿 java伐憾、go 與 js 做對比的。)
1 js 中為什么浮點數(shù)也能參與位運算
這在 java赫模、go树肃、c 中都是不被允許的
* 6 種 基本類型:
1. Boolean
2. Null
3. Undefined
4. Number
5. String
6. Symbol (ECMAScript 6 新定義)
細心的人已經(jīng)發(fā)現(xiàn),基本類型里并沒有浮點型瀑罗。
事實上在 js 中的 Number 類型是不區(qū)分 int胸嘴、long、float斩祭、double 類型的(go 的用戶們就呵呵一笑了劣像,來來來,我們的浮點型就能王炸你)摧玫《龋回正題,不區(qū)分整型诬像、浮點型那怎么存儲呢屋群,為了不丟失精度, js 中的 Number 類型實際上一個基于 IEEE 754 標準的雙精度64位浮點數(shù)(java 的同學(xué)就把它當成 double 看)坏挠∩瞩铮看到這我想很多人應(yīng)該能明白為什么 js 里浮點數(shù)也能參與位運算了吧。這也是沒有辦法降狠,因為對于內(nèi)存來說整型对竣、浮點型都沒有區(qū)別了。
這里是有一個問題的榜配,因為當 js 需要進行位運算時否纬,會將操作數(shù)通通轉(zhuǎn)成 32 位比特序列(0,1)芥牌,也就是補碼烦味。操作完成之后,再按照 64 位浮點數(shù)存儲
2 那么 js 做位運算時壁拉,小數(shù)部分怎么處理呢?
注意這里說的全部位運算
直接丟棄0匕小F怼! 曾吶屎蜓!這么虎痘昌?
沒錯,就是這么暴力,那么問題來了辆苔,既然小數(shù)部分不參與位運算算灸,那么為什么不能像 java、go 那樣直接禁止呢驻啤?關(guān)于這個問題菲驴,我想那就是語言設(shè)計者的想法,我就不知道了骑冗。但是這其實也帶來了一些特別的操作赊瞬,比如在 js 中雙取反是可以做取整操作的。
~~2.2 // 2
~~2.8 // 2
3 js 中非數(shù)值類型如何進行位運算的呢贼涩?
當 js 需要進行位運算的時候巧涧,對于非數(shù)值類型,會首先將操作數(shù)轉(zhuǎn)成一個整型(就是0)然后在進行運算遥倦。這就解釋了為什么 js 中可以允許非數(shù)值類型參與運算谤绳,其實這是個偽命題,因為實質(zhì)上是對非數(shù)值操作數(shù)的整型表達式進行的位運算袒哥。
這里需要注意闷供,上面說過了 js 中的整型在內(nèi)存中都是一個 64 位雙精度浮點型,但是 js 進行位運算時统诺,會將操作數(shù)轉(zhuǎn)成帶符號位的 32 位比特序列(0歪脏,1),也就是補碼粮呢。運算結(jié)束后婿失,再按照 64 位存儲。那么問題來了啄寡,這里肯定會存在精度丟失對吧豪硅,這應(yīng)該不難理解。js 確實也是這樣處理的挺物,超過 32 位的部分直接截斷懒浮。
所以對一個非數(shù)值變量做取反操作,得到的一定是 -1识藤,因為實際上等于對 0 做取反操作砚著。
4 js 中的無符號右移到底有什么特別之處?
首尾呼應(yīng)一下痴昧,畢竟就是這個問題使我查資料寫了這篇文章稽穆。
首先解釋一下,>>> 無符號右移原本是 java 里特有的(這里是和 js赶撰、go 對比舌镶,其他語言我沒用過柱彻,不能亂說)。js 中的無符號右移跟 java 幾乎一樣餐胀,除了一點兩種語言處理方式完全不一樣哟楷。
那就是并沒有真正發(fā)生移位的情況下,符號位會不會被替換成0否灾。java 中是不會替換的卖擅,但是 js 中是會發(fā)生替換的。
當操作數(shù)是正數(shù)的時候坟冲,不管有沒有真的移位并沒有區(qū)別磨镶,因為正數(shù)的符號位是 0。
當操作數(shù)是負數(shù)時健提,移動位數(shù)大于0琳猫,也體現(xiàn)不出區(qū)別:
// java
5 >>> 0 // 5
5 >>> 1 // 2
-1 >>> 1 // 2147483647
// js
5 >>> 0 // 5
5 >>> 1 // 2
-1 >>> 1 // 2147483647
但是當操作數(shù)是負數(shù),無符號右移 0 位時私痹,區(qū)別就大了:
// java
-1 >>> 0 // -1
// js
-1 >>> 0 // 4294967295
這是因為 -1 的補碼是:
11111111111111111111111111111111
>>>0 實際上并沒有發(fā)生數(shù)位變化脐嫂,但是 js 卻會把符號位替換成 0,
// java
11111111111111111111111111111111
// js
01111111111111111111111111111111
此時原來負數(shù)的補碼紊遵,變?yōu)榱苏龜?shù)的源碼(這就是為什么 js 中 -1>>>0 會變成一個巨大的正整數(shù))账千。
js 中無符號右移時,不管正數(shù)暗膜、負數(shù)都會首先將符號位替換成 0匀奏,然后再進行移位。也就是說学搜,該運算符永遠返回正整數(shù)娃善。
總結(jié)
js 的位運算,為什么會有這么多奇怪的地方呢瑞佩?我相信很多同學(xué)都會有這種想法聚磺,特別是 java 的同學(xué)們吧。為此我查了 js 的歷史炬丸。
1995 Sun 公司正式發(fā)布 java 語言瘫寝,當時的網(wǎng)景公司正在為它們的 Navigator 瀏覽器尋找一種網(wǎng)頁腳本(此前的瀏覽器不具備互動能力)。當他們看到 Sun 公司的宣傳后稠炬,與 Sun 合作開發(fā)全新的腳本語言 javascript 焕阿。此前我一直不明白 js 既然不是 java 的腳本,為什么叫這個名字∷岣伲現(xiàn)在懂了捣鲸,因為當時新腳本語言的決策中,Sun 公司占了很大一環(huán)闽坡。
1995年5月 按照公司的要求(一個像 java 但是比 java 簡單的腳本語言)栽惶,Brendan Eich 僅用10天就寫出了 javascript。
在我們膜拜大神的時候疾嗅,也要認清一個現(xiàn)實外厂,當時給 Brendan Eich 的時間太短了,所以很多問題并沒有很好的解決代承,而且一邊模仿 java汁蝶、c,一邊還要簡化數(shù)據(jù)類型论悴、內(nèi)存模型掖棉。我覺得這就是為什么 js 的位運算這么奇怪的原因。
js 完全套用了 java 的位運算符膀估。
但是 java 的位運算是針對整數(shù)的幔亥,對 js 沒什么用啊,因為 js 中察纯,所有數(shù)字都保存為雙精度浮點型帕棉。如果使用它們的話,js 不得不將操作數(shù)先轉(zhuǎn)為整數(shù)饼记,然后再進行運算香伴。
所以很多人不建議在 js 中使用位運算,理由是 js 天生就會進行類型轉(zhuǎn)換具则,使得效率降低即纲。