前端利用二进制码表示状态

2,518 阅读3分钟

在前端中,二进制码似乎在平常的开发中很少会去使用到,但是在一些框架中,也会看到其作为开发的一部分存在,并且结合位运算,有时也能达到较好的使用效果。

// react\packages\shared\ReactSideEffectTags.js 中使用的相关状态码

export type SideEffectTag = number;

// Don't change these two values. They're used by React Dev Tools.
export const NoEffect = /*              */ 0b00000000000;
export const PerformedWork = /*         */ 0b00000000001;

// You can change the rest (and add more).
export const Placement = /*             */ 0b00000000010;
export const Update = /*                */ 0b00000000100;
export const PlacementAndUpdate = /*    */ 0b00000000110;
export const Deletion = /*              */ 0b00000001000;
export const ContentReset = /*          */ 0b00000010000;
export const Callback = /*              */ 0b00000100000;
export const DidCapture = /*            */ 0b00001000000;
export const Ref = /*                   */ 0b00010000000;
export const Snapshot = /*              */ 0b00100000000;

简单的位运算

位运算不会进位,不要与二进制的加减运算混淆

 与(AND)      或(OR)      异或(XOR)     非(NOT)

  0b001       0b001        0b011        
& 0b010     | 0b010      ^ 0b001      ~ 0b011
--------    --------     --------     --------
  0b000       0b011        0b010        0b100

在js中的数字类型转换中:

  • 0,NaN对应的boolean值为false
  • 123,-123,Infinity,...对应的boolean值为true

所以利用这一特性可以将二进制数位运算的值转换为boolean值。

运用位运算实例

吃饭,睡觉,打豆豆为例

// 存在的各种状态(为了对齐,借鉴React的书写方式)
const Eat = /*      */ 0b001 // 吃饭
const Sleep = /*    */ 0b010 // 睡觉
const Fight = /*    */ 0b100 // 打豆豆

const Mode = /*     */ 0b000 // 用户的初始状态

let eatAndSleep = Mode | Eat | Sleep // 0b011
let emptyMode = Mode // 0b000
let fightMode = Mode | Fight // 0b100

// 检验是否吃饭
console.log(eatAndSleep & Eat) // 1 (0b001) -> true
console.log(emptyMode & Eat) // 0 (0b000) -> false
console.log(fightMode & Eat) // 0 (0b000) -> false

// 检验是否打豆豆
console.log(eatAndSleep & Fight) // 0 (0b000) -> false
console.log(emptyMode & Fight) // 0 (0b000) -> false
console.log(fightMode & Fight) // 4 (0b100) -> true

添加类型

let mode = Mode | Eat // 增加吃饭的标记 0b001

是否有类型

let hasEat = Mode & Eat // 结果0或者非0
// let hasEat = !!(Mode & Eat) // 结果为true/false

去除类型

mode = mode ^ Eat // 去除睡觉的标记 0b000

mode = mode & ~(Eat) // 去除睡觉的标记
mode = mode & ~(Eat | Sleep) // 去除 睡觉,吃饭 的标记

更为通用的方法

/**
 * 判断是否存在当前的状态
 * @param  {Number}  currentMode 当前的状态
 * @param  {Number}  targetMode  目标状态
 * @return {Boolean}             是否存在
 */
function hasMode(currentMode, targetMode) {
  return (currentMode & targetMode) !== 0
}

/**
 * 为当前状态码添加状态
 * @param  {Number}  currentMode 当前的状态
 * @param  {Number}  targetMode  目标状态
 * @return {Number}              新生成的状态吗
 */
function addMode(currentMode, targetMode) {
  return currentMode | targetMode
}

/**
 * 为当前状态码去除状态
 * @param  {Number}  currentMode 当前的状态
 * @param  {Number}  targetMode  目标状态
 * @return {Number}              新生成的状态吗
 */
function removeMode(currentMode, targetMode) {
  // return currentMode & ~targetMode // 也可使用
  return currentMode ^ targetMode // 可使用 ^异或去除
}

使用方式

使用封装的方法

let eatMode = addMode(Mode, Eat)
let eatAndSleepMode = addMode(eatMode, Sleep)
console.log(hasMode(eatAndSleepMode, Eat)) // true

let sleepMode = removeMode(eatAndSleepMode, Eat)
console.log(hasMode(sleepMode, Eat)) // false

直接使用二进制操作

React源码中大量使用到了位操作

!!操作可以直接转换为boolean值

let eatAndSleepMode = Mode | Eat | Sleep
console.log(!!(eatAndSleepMode & Eat)) // true

let sleepMode = eatAndSleepMode & ~Eat
console.log(!!(sleepMode & Eat)) // false

有时React会直接操作当前mode

effectTag |= Ref // 将Ref标识添加到effectTag中

effectTag &= ~Placement // 将Placement状态从effectTag中去除