React 源碼中的位運算技巧
大家好,我卡頌。
這兩年有不少朋友和我吐槽React
源碼,比如:
-
調度器爲什麼用小頂堆這種數據結構,直接用數組不行?
-
源碼裏各種單向鏈表、環狀鏈表,直接用數組不行?
-
源碼裏各種位運算,有必要麼?
作爲業務依賴的框架,爲了提升一點點運行時性能,React
從不吝惜將源碼寫的很複雜。
在涉及狀態
、標記位
、優先級
操作的地方大量使用了位運算。
本文會講解其中比較有代表性的部分。學到之後,當遇到類似場景時露一手,你就是業務線最靚的仔。
幾個常用位運算
在JS
中,位運算
的操作數會先轉換爲Int32
(32 位有符號整型),執行完位運算
會Int32
對應浮點數。
在React
中,主要用到 3 種位運算符 —— 按位與、按位或、按位非。
按位與(&)
對於兩個二進制操作數的每個bit
,如果都爲 1,則結果爲 1,否則爲 0。
舉個例子,計算3 & 2
,首先將操作數轉化爲Int32
:
// 3對應的 Int32
0b000 0000 0000 0000 0000 0000 0000 0011
// 2對應的 Int32
0b000 0000 0000 0000 0000 0000 0000 0010
爲了直觀,我們排除前面的 0,只保留最後 8 位(實際參與計算的應該是 32 位):
0000 0011
& 0000 0010
-----------
0000 0010
所以3 & 2
計算結果轉化爲浮點數後爲 2。
按位或(|)
對於兩個二進制操作數的每個bit
,如果都爲 0,則結果爲 0,否則爲 1。
計算10 | 3
:
0000 1010
| 0000 0011
-----------
0000 1011
計算結果轉化爲浮點數後爲 11。
按位非(~)
對一個二進制操作數的每個bit
,逐位進行取反操作(0、1 互換)
對於~3
,將 3 轉化爲Int32
後逐位取反:
// 3對應的 Int32
0b000 0000 0000 0000 0000 0000 0000 0011
// 逐位取反
0b111 1111 1111 1111 1111 1111 1111 1100
計算結果轉化爲浮點數後爲 - 4。
如果你對這個結果有疑惑,可以去了解
補碼
相關知識
讓我們從易到難,看看位運算在React
中的應用。
標記狀態
React
源碼內部有多個上下文環境,在執行函數時經常需要判斷當前處在哪個上下文環境中。
假設共有三種上下文情況:
// A上下文
const A = 1;
// B上下文
const B = 2;
// 沒有處在上下文
const NoContext = 0;
當進入某個上下文時,可以使用按位或
操作標記進入:
// 當前所處上下文
let curContext = 0;
// 進入A上下文
curContext |= A;
我們用 8 位二進制舉例(同樣,實際應該是 Int32,這裏是爲了簡化),curContext
與A
執行按位或
操作:
0000 0000 // curContext
| 0000 0001 // A
-----------
0000 0001
此時可以結合按位與
操作與NoContext
來判斷是否處在某一上下文中:
// 是否處在A上下文中 true
(curContext & A) !== NoContext
// 是否處在B上下文中 false
(curContext & B) !== NoContext
離開某上下文後,結合按位與
、按位非
移除標記:
// 從當前上下文中移除上下文A
curContext &= ~A;
// 是否處在A上下文中 false
(curContext & A) !== NoContext
curContext
與~A
執行按位與
操作:
0000 0001 // curContext
& 1111 1110 // ~A
-----------
0000 0000
即從curContext
中移除A
。
當業務中需要同時處理多個狀態時,可以使用如上位運算技巧。
優先級計算
在React
中,不同情況下調用this.setState
觸發的更新會擁有不同優先級。優先級之間的比較、挑選同樣使用了位運算。
具體來說,React
中用 31 個bit
位保存**「更新」**(之所以是 31 而不是 32 是因爲Int32
的最高位是符號位,不保存具體的數)。
處在越低bit
位的更新優先級越高(越需要優先處理)。
舉個例子,假設當前應用存在 2 個更新:
0b000 0000 0000 0000 0000 0000 0001 0001
其中第 1 位的更新優先級最高(需要同步處理),第 5 位爲默認優先級。
React
經常需要找出當前最高優先級的更新在哪一位(如上例子中在第一位),方法如下:
function getHighestPriorityLane(lanes) {
return lanes & -lanes;
}
解釋下,由於Int32
採用**「補碼」**表示,所以-lanes
可以看作如下兩步操作:
-
lanes 取反(~lanes)
-
加 1
爲了直觀,用 8 位表示:
lanes 0001 0001
~lanes 1110 1110 // 第一步
+1 1110 1111 // 第二步
則lanes & -lanes
如下:
0001 0001 // lanes
& 1110 1111 // -lanes
-----------
0000 0001
取到的就是第一位(已有更新中最高的優先級)。
總結
雖然業務中不常使用位操作,但在特定場景下位操作時很方便、高效的方式。
這波操作你愛了麼?
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/zrfmXGHK4PG8B6pdGYO6Aw