一篇看懂位运算在 React 中的应用

745 阅读5分钟

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

今天会聊哪些内容
  • 什么是 Bitfield
  • 用于 BitField 来处理多状态
  • BitField 在 React 项目中的应用

什么是 Bit field

位域或者叫做位段(Bit field)为一种数据结构,可以把数据以位的形式紧凑的储存起来,可以节省储存空间减少处理的负担,当程序需要成千上万个数据单元时,这种方法就显得尤为重要。那么应该如何定义一个 bitField

const a = 0b00100
// 2^0 * 1 + 2^1 * 0 + 2^2*1 = 1 + 0 + 4 = 5
console.log(a)

javascript 也提供定义 bitFields ,0b 前缀表示是 bitField 的类型数据。 0b00100。然后我们通过一个例子来解释一下如何使用 bitField 这种数据来解决一些实际问题。

用于 BitField 来处理多状态

位域是一个变量,可以将 bitField 每个位对应一个布尔状态,也就是将布尔状态用 BitField 的一个位保存,这里关键有点信息熵,位数越多 bitField 包含信息量就越大。在下面例子中,用 bitField 来表示键盘状态,看看通过 bitField 表示状态有哪些好处,

例如,监听用户操作,当监听用户按键盘上的方向键上、下、左、右,可以将各种按键编码为 bitField 的变量,每个方向分配一个位。

var bitField = 0;  // 定义变量用于接受 bitField 类型数据
const KEY_BITS = [4,1,8,2]; // left up right down
const KEY_MASKS = [0b1011,0b1110,0b0111,0b1101]; // left up right down

window.onkeydown = window.onkeyup = function (e) {
    if(e.keyCode >= 37 && e.keyCode <41){
        if(e.type === "keydown"){
            bitField |= KEY_BITS[e.keyCode - 37];
        }else{
            bitField &= KEY_MASKS[e.keyCode - 37];
        }
    }    
}

我们知道 left up right down 按下这些键对应 keyCode 码分别是 37, 38, 39, 40 然后如果对 keyCode - 37 就得到了 0, 1, 2, 3 来对应这个按键。然后我们用 bitField 不同位表示键盘哪个方向键是否被按下,分别用 0b0100, 0b0001,0b1000,0b0010 表示 left, up, right, down 四个方向键,也就是利用 bitField 不同位的值存储方向键按下状态 0 表示方向键释放,1 表示按下。

  • | 或操作符: 如果两位之一为 1 则设置每位为 1
  • & 与操作符: 如果两位都是 1 则设置每位为 1

所以KEY_MASKS 对应操作会改变bitField数据表示每一个方向对应位置位上值。

var directionState = [false,false,false,false];
window.onkeydown = window.onkeyup = function (e) {
  if(e.keyCode >= 37 && e.keyCode <41){
    directionState[e.keyCode - 37] = e.type === "keydown";
  }    
  console.log(directionState)

}

当然也可以用这样一个布尔值所组成数组来表示对应方向键键是否按下,那么这么一看通过 bitField 来表示方向键按下状态是否有点复杂,而用于布尔值所组成数组来表示方向键状态不是更简单更直观吗? 接下来就会发现通过 BitField 保存多状态的好处了。

接下来我们如果想要检测是否某一个方向键被按下,对于 BitField 数据类型一个 if(!bitField) 就可以实现检测是否方向按键被按下,对于数组形式这需要 if(!(directionState[0] && directionState[1] && directionState[2] && directionState[3])),通过 BitField 位表示状态方式,可以简化许多处理任务。

const KEY_U = 1;//0b0000
const KEY_D = 2;//0b0001
const KEY_L = 4;//0b0010
const KEY_R = 8;//0b0100
const KEY_UL = KEY_U + KEY_L; // up left 0b0101
const KEY_UR = KEY_U + KEY_R; // up Right
const KEY_DL = KEY_D + KEY_L; // down left
const KEY_DR = KEY_D + KEY_R; // down right

// 0b0101
console.log(KEY_UL)

这里一看通过 BitField 表示状态,很方便表示状态组合从而得到更多状态

BitField 在 React 中应用

那么用 BitField 数据结构来表示状态有什么最佳实践,其实现在 React 中就是使用到了 BitField 格式存储 effectTag 标签。

effectTag

我们知道当 state 和 props 变化时,会引起视图的重新渲染,这个过程叫做 side effect(侧边效应),而 side effect 分为很多种情况,具体要执行哪种 effect,在 React 中是通过 effectTag 属性记录的,在源码中以 BitField 形式来保存

具体定义方法如下

let effectTag = 0b01010001;//81
NoEffect: 0, //           0b0000000
Placement: 1, //          0b0000001
Update: 2, //             0b0000010
PlacementAndUpdate: 3, // 0b0000011
Deletion: 4, //           0b0000100
ContentReset: 8, //       0b0001000
Callback: 16, //          0b0010000
Err: 32, //               0b0100000
Ref: 64, //               0b1000000
  • Placement:向树中插入新的子节点,对应的状态为MOUNTING,当执行commitPlacement函数完成插入后,清除该标志位
  • Update:当props、state、context发生变化,或者forceUpdate时,会标记为Update,检查到标记后,执行commitUpdate函数进行属性更新,与其相关的生命周期函数为componentDidMount和componentDidUpdate

像这样定义 effectTag,可以方便地使用二进制操作。(通过 effectTag |= Placement 添加新的标签,通过 effectTag &= ~Placement 进行删除)

const effectTags = {
    Placement : 0b000000000010,
    Update : 0b000000000100,
    PlacementAndUpate : 0b000000000110,
    ...
}

React 对 DOM 更新操作进行了编码,接下来 React 将检查每个位,并查看其对应要执行什么类型的操作。例如,update 将更新标记。

function updateHostEffects(fiberNode){
    const effectTag = fiberNode.effectTag;

    if(effectTag & effectTags.Placement){};
    if(effectTag & effectTags.Update){};
    if(effectTag & effectTags.PlacementAndUpate){};
}

总结

Bitfield 是比较底层的一种数据结构。可以理解为全部是由 0 和 1 组成的数组,熟悉 c++ 编程可能对这部分内容不会感觉陌生,其实 Bitfield 就是一种数据结构。

例如,使用 Bitfield 数据结构,就不需要为 JavaScript 对象和 shape 分配内存,因此虚拟机可以节省大量空间,而且由于没有 shape 和 JavaScript对象,因此没有引用,垃圾回收也变的简单得多。不然根据对象依赖关系图,知道哪些对象是否可以被安全回收的。对于bitfils ,一个指令就让处理器清除存储器,就是这样简单。

通过上面例子大家不难看出 BitField 的具体应用,用于保存多状态,这种方式简化对多任务处理和运算,也适合保存权限,有关 BitField 在保存权限上好处这里暂时不做过多介绍,随后会详细介绍。

Bitfield 使用一个较小的连续内存,并实现对其快速的访问。因此在 Angular 和 React 中都使用了这种数据结构。