愛伊米

React原始碼中的位運算技巧

作者:卡頌

簡介:《React技術揭秘》作者

大家好,我卡頌。

這兩年有不少朋友和我吐槽 React 原始碼,比如:

排程器為什麼用小頂堆這種資料結構,直接用陣列不行?

原始碼裡各種單向連結串列、環狀連結串列,直接用陣列不行?

原始碼裡各種位運算,有必要麼?

作為業務依賴的框架,為了提升一點點執行時效能,React

從不吝惜將原始碼寫的很複雜。

在涉及狀態、標記位、優先順序操作的地方大量使用了位運算。

本文會講解其中比較有代表性的部分。學到之後,當遇到類似場景時露一手,你就是業務線最靚的仔。

幾個常用位運算

在 JS 中,位運算的運算元會先轉換為Int32(32位有符號整型),執行完位運算會Int32對應浮點數。

在React中,主要用到3種位運算子 —— 按位與、按位或、按位非。

按位與(&)

對於兩個二進位制運算元的每個bit,如果都為1,則結果為1,否則為0。

舉個例子,計算3 & 2,首先將運算元轉化為 Int32:

為了直觀,我們排除前面的0,只保留最後8位(實際參與計算的應該是32位):

所以3 & 2計算結果轉化為浮點數後為2。

按位或(|)

對於兩個二進位制運算元的每個bit,如果都為0,則結果為0,否則為1。

計算10 | 3:

計算結果轉化為浮點數後為11。

按位非(~)

對一個二進位制運算元的每個 bit,逐位進行取反操作(0、1互換)

對於~3,將3轉化為 Int32 後逐位取反:

計算結果轉化為浮點數後為-4。

如果你對這個結果有疑惑,可以去了解補碼相關知識

讓我們從易到難,看看位運算在React中的應用。

標記狀態

React原始碼內部有多個上下文環境,在執行函式時經常需要判斷當前處在哪個上下文環境中。

假設共有三種上下文情況:

當進入某個上下文時,可以使用按位或操作標記進入:

我們用8位二進位制舉例(同樣,實際應該是Int32,這裡是為了簡化),curContext與A執行按位或操作:

此時可以結合按位與操作與NoContext來判斷是否處在某一上下文中:

離開某上下文後,結合按位與、按位非移除標記:

curContext與~A執行按位與操作:

即從curContext中移除A。

當業務中需要同時處理多個狀態時,可以使用如上位運算技巧。

優先順序計算

在React中,不同情況下呼叫this。setState觸發的更新會擁有不同優先順序。優先順序之間的比較、挑選同樣使用了位運算。

具體來說,React中用31個bit位儲存更新(之所以是31而不是32是因為Int32的最高位是符號位,不儲存具體的數)。

處在越低bit位的更新優先順序越高(越需要優先處理)。

舉個例子,假設當前應用存在2個更新:

其中第1位的更新優先順序最高(需要同步處理),第5位為預設優先順序。

React經常需要找出當前最高優先順序的更新在哪一位(如上例子中在第一位),方法如下:

解釋下,由於Int32採用補碼錶示,所以-lanes可以看作如下兩步操作:

lanes取反(~lanes)

加1

為了直觀,用8位表示:

則lanes & -lanes如下:

取到的就是第一位(已有更新中最高的優先順序)。

總結

雖然業務中不常使用位操作,但在特定場景下位操作時很方便、高效的方式。

這波操作你愛了麼?