最近在閱讀Koa2源碼,在閱讀過程當中get到了一些特別的技巧:就是關於~這個傢伙的。爲此作了一些調研,把學到的東西分享給你們~~javascript
非科班出身的同窗可能會對這個~符號比較懵,這裏簡單介紹一下(科班同窗就能夠跳過啦)。~是位運算符的一種,它的做用是按位取反。java
咱們都知道,計算機其實只認兩個符號:0、1,但其實在計算機中,數值一概是用補碼來表示和存儲,所以位運算也是基於補碼運算的。git
這裏又涉及到了補碼的概念,因此簡單介紹一下原碼、補碼:github
JS中的~與其餘語言中的~略有不一樣,這裏先簡單介紹一下比較常規的~。數組
在對正數進行~操做時:函數
舉個實例:性能
對負數進行~操做時:測試
舉個實例:ui
總結:~x = -x - 1,如 ~3 = -3-1 = -4 ;~(-3) = 3 - 1 = 2es5
而在JS裏,~會首先對運算對象進行轉換爲整數的操做,轉換規則參考ECMA-262規範的ToInt32。這裏簡單翻譯一下,假設咱們輸入的參數爲input;
舉個例子:~undefined,在轉換過程當中,ToNumber(undefined)結果爲NaN,ToInt32(NaN)結果爲+0,所以~undefined結果與~+0一致,爲-1。另外對於對象的轉換,核心規則主要根據[[DefaultValue]] (hint)。下面各舉一些對象轉換的例子:
> ~{}
-1
> ~[]
-1
> ~NaN
-1
> ~[1]
-2
> ~[2,3]
-1
> ~{toString: () => '45'}
-46
> ~{toString: () => '45',valueOf: () => 123}
-124
複製代碼
const target = [1,4,56,7]
console.log(!!~target.indexOf(8)) //false
// 經過~轉換爲0後,不少處理能夠簡化,
// 能夠免去寫 !== -1 或者 === -1等煩惱
複製代碼
const target = '321'
console.log(typeof ~~target) // number
複製代碼
const target1 = '321.235'
console.log(~~target1) // 321
const target2 = '-321.77'
console.log(~~target2) // -321
複製代碼
> ~~undefined == !!undefined
true
> ~~null == !!null
true
> ~~0 == !!0
true
> ~~1 == !!1
true
// 注意,並非全部同樣的輸入,~、!計算結果都相等
> ~~'a' == !!'a'
false
> ~~[0] == !![0]
false
> ~~-1 == !!-1
false
> ~~2 == !!2
false
複製代碼
上面提到了~的幾種用處,歸根結底都是利用了~能快速轉換整數的能力,下面分別對~~與其它經常使用的轉換操做進行簡單的速度比較,測試環境:macOS Mojave 10.14.2 Node v8.12.0
let {numArr,stringArr} = require('./data');
let func1 = number => {
let start = performance.now();
let b = ~~number; // 測試符號:包括 ~ 、Math.floor、parseInt、+ 四種
return {
value: b,
time: performance.now() - start
};
};
let totalTime = 0;
numArr.map(item => {
totalTime += func1(item).time
})
console.log({
type: 'fun1',
totalTime,
len: numArr.length,
average: totalTime / numArr.length
})
複製代碼
因爲篇幅問題,這裏不放全全部代碼了,主要修改內容爲有測試的運算符號以及數據來源。數據爲MockJS生成隨機浮點數數組,包含正負浮點數、正負浮點字符串、隨機Null、undefined、NaN數組;
操做符號 | 測試數量 | 總耗時 | 平均時間 |
---|---|---|---|
~~ | 609 | 0.24905507266521454 | 0.00040895742637966264 |
Math.floor | 609 | 0.34583795070648193 | 0.00056787840838502780 |
parseInt | 609 | 0.28167189657688140 | 0.00046251542951868867 |
+ | 609 | 0.31073787808418274 | 0.00051024282115629350 |
操做符號 | 測試數量 | 總耗時 | 平均時間 |
---|---|---|---|
~~ | 701 | 0.4971509501338005 | 0.0007092024966245371 |
Math.floor | 701 | 0.5587870776653290 | 0.0007971284988093138 |
parseInt | 701 | 0.5465480908751488 | 0.0007796691738589855 |
+ | 701 | 0.4985470473766327 | 0.00071119407614355590 |
操做符號 | 測試數量 | 總耗時 | 平均時間 |
---|---|---|---|
~~ | 2187 | 2.5649426132440567 | 0.0011728132662295642 |
!! | 2187 | 2.2864151149988170 | 0.0010454572999537344 |
在JS中,因爲有參數的轉換,~~必然會存在性能的損耗,可是這個損耗在咱們能夠接受的範圍內,對比上面幾組測試結果可得知,同是數字的狀況下,~速度最快,而其餘兩種狀況因爲轉換的緣由,略有下降。
在測試過程當中,還發現了一個有意思的事,發現用console、performance測量時,第一個耗時較長,有圖有真相:
第一、2張圖是console.time、performance.now計算的結果,在第二張圖時還互換了下兩個函數調用順序。猜想是第一次調用console.time、performance須要進行某些初始化。