寫在最前
本次分享一下并不是很常用的按位非運算符~的原理以及一點點用法。
歡迎關注我的博客,不定期更新中——
JavaScript小眾系列開始更新啦
——何時完結不確定,寫多少看我會多少!這是已經更新的地址:
- 小眾系列之按位非運算符:JavaScript中奇特的~運算符
- 小眾系列之終極類型轉換:從hello world看JavaScript隱藏的黑魔法制
- 小眾系列之隱式類型轉換:從[] == ![]看隱式強制轉換機制
- 小眾系列之事件循環:從HTML5與PromiseA+規范來看事件循環
這個系列旨在對一些人們不常用遇到的知識點,以及可能常用到但不曾深入了解的部分做一個重新梳理,雖然可能有些部分看起來沒有什么用,因為平時開發真的用不到!但個人認為糟粕也好精華也罷里面全部蘊藏著JS一些偏本質的東西或者說底層規范,如果能適當避開舒適區來看這些小細節,也許對自己也會有些幫助~文章更新在我的博客,歡迎不定期關注。
~的規則是什么
看下規范里面的定義的~:
產生式 UnaryExpression : ~ UnaryExpression 按照下面的過程執行:
令 expr 為解釋執行 UnaryExpression 的結果。
令 oldValue 為 ToInt32(GetValue(expr))。
返回 oldValue 按位取反的結果。結果為 32位 有符號整數。
總結一下即將數字進行抽象Toint32操作,再進行按位取反。那么再來看下關于Toint32:
數字進行Toint32操作會轉化成32位有符號數,第一位為符號位,后面31位為表示整數數值。最后對數字進行按位取反即可得到~轉換后的結果。
舉個??
//以18為例子,進行Toint32抽象操作
//將18表示為二進制形式
0 000 0000 0000 0000 0000 0000 0001 0010
//|符號位|| 數值部分 |
//按位取反
1 111 1111 1111 1111 1111 1111 1110 1101
//|符號位|| 數值部分 |
可以發現現在將18進行了按位非操作之后這個數變成了一個負數,同時我們可以看到這么多個1。。感覺這個負數很大啊?所以~18會是一個很大的負數么?我們打印看下:
好像和預料中的有些出入?
負數是如何存成二進制的?
我們可以直接打印看下:
然而這并不是我們想要的,會有這個結果是因為ECMAScript采用了這樣簡單的方式來避免開發者接觸一些底層的操作,真實的存儲二進制負數的方式應該是采用補碼的形式。而也正是由于補碼的操作我們才能解釋為什么~18 === -19
補碼
生成補碼的三個步驟:
確定該數字的非負版本的二進制表示(例如,要計算 -18的二進制補碼,首先要確定 18 的二進制表示)
求得二進制反碼,即要把 0 替換為 1,把 1 替換為 0
在二進制反碼上加 1
我們先不管為什么負數要用補碼來存儲,先來看下~18 === -19是如何而來的。
根據上述計算-19的補碼步驟:
//將19表示為二進制形式
0 000 0000 0000 0000 0000 0000 0001 0011
//|符號位|| 數值部分 |
//按位取反
1 111 1111 1111 1111 1111 1111 1110 1100
//|符號位|| 數值部分 |
//反碼加一
1 111 1111 1111 1111 1111 1111 1110 1100
1
--------------------------------------------
1 111 1111 1111 1111 1111 1111 1110 1101
//同時 18的按位取反表示為:
1 111 1111 1111 1111 1111 1111 1110 1101
所以我們可以看到,由于補碼為按位取反并+1,~ 為按位取反,那么也就可以說明為什么~18 === -19 同時我們也可以得出結論即:
~x === -(x+1)
那么為什么負數存儲為補碼?
因為計算機在做二進制運算的時候,不希望考慮運算數的符號,全部希望執行加法操作來得出正確結果,由此引入了補碼的概念。比如我們試圖用4-2的結果與4+2的補碼結果比對來進行說明:
4 - 2 =>
0100 - 0010 = 0010
4 + (-2) =>
0010 + 1110 = 0010(相加超過位數,溢出自動丟失)
~的應用
對哨位值進行~操作
哨位值一般可以表示失敗的意思。例如js中的哨位值如-1,當你執行indexOf操作時,如果找不到目標則返回-1,同時~-1 = 0,由此我們可以將代碼轉變為:
if(str.indexOf('js') != -1) => if(~indexOf('js'))
那么為什么不使用>=0或者!= -1這種操作呢,在《你不知道的JavaScript》一書中,將之成為“抽象滲漏”,意思是在代碼中暴露了底層實現細節,我們可以選擇屏蔽掉細節。故 ~ 可以和indexOf進行配合判斷真假值,核心思路就是運用了~x === -(x+1)
浮點數取整
我們現在知道~ 會進行按位取反的過程中會進行Toint32抽象操作,在這個過程中會將浮點數去掉,只對前面32位整數進行處理。故我們可以使用~進行以下操作:
~~3.12 = 3
同時需要注意由于~的特性,小數點后面的部分是直接被干掉的,而不是會進行Math.floor之類的四舍五入操作。
參考資料
最后
慣例po作者的博客,不定時更新中——
有問題歡迎在issues下交流。