Bitwise operations in React

 Basics of Bitwise Arithmetic

 The term binary refers to a way of counting with two as the base.

hexadecimal 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
binary 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
octal 0 1 2 3 4 5 6 7 10 11 12 13 14 15 16 17
 hexadecimal 0 1 2 3 4 5 6 7 8 9 A B C D E F


We often use binary to calculate, based on the binary bit operation can be very convenient to express “add, delete, check, change”.


For example, a back-end management system, in general, there will be for the control of permissions, the general control of permissions on the use of binary:

# 各个权限
permissions = {
    "SYS_SETTING" : {
        "value" : 0b10000000,
        "info" : "Important system setup privileges"
    },
    "DATA_ADMIN" : {
        "value" : 0b01000000,
        "info" : "Database administration privileges"
    },
    "USER_MANG" : {
        "value" : 0b00100000,
        "info" : "user management privileges"
    },
    "POST_EDIT" : {
        "value" : 0b00010000,
        "info" : "Article editing privileges"
    },
    "POST_VIEW" : {
        "value" : 0b00001000,
        "info" : "Article Viewing Privileges"
    }
}


For example, in the linux operating system, x is executable, w is writable, and r is readable, and the corresponding permissions are 1, 2, and 4 (powers of 2).


Using binary to represent permissions is, firstly, faster, and secondly, more convenient when representing multiple permissions.


For example, there are now 3 permissions A, B, C…

 Do different things based on different permissions:

if(value === A){
  // ...
} else if(value === B){
  // ...
}


In the above code, there will be a problem, currently only one-to-one relationship, but in the actual development, there are often a lot of one-to-many relationships, a value may correspond to several values.

 Review the operations associated with binary:


  • with ( & ): as long as one of the digits is 0, then the final result is 0, that is, both digits must be 1 for the final result to be 1

  • or ( | ): as long as one of the digits is 1, then the final result is 1, i.e. both must be 0 for the final result to be 0

  • not ( ~ ): inverts a binary number bit by bit, i.e., 0 and 1 are interchanged.

  • Different or (^): If two binary bits are not the same, then the result is 1, and if they are the same, it is 0.
1 & 1 = 1

0000 0001
0000 0001
---------
0000 0001

1 & 0 = 0

0000 0001
0000 0000
---------
0000 0000

1 | 0 = 1

0000 0001
0000 0000
---------
0000 0001

1 ^ 0 = 1

0000 0001
0000 0000
---------
0000 0001

~3
0000 0011
1111 1100


Next, let’s look at the practical use of bitwise operations inside the permissions system:

download print view process detail delete edit create
0 0 0 0 0 0 0 0


If 0, you don’t have permission, if 1, you do.


0000 0001 means only create access, 0010 0011 means view, edit and create access

 Add Permissions

 Just use the or operation directly.


0000 0011 currently has create and edit permissions, we’re going to add a view permission to him 0010 0000

0000 0011
0010 0000
---------
0010 0011

 Deletion of Privileges

 You can use an iso-or


0010 0011 Currently have view, edit and create, un-edit permissions 0000 0010

0010 0011
0000 0010
---------
0010 0001

 Determine if a certain permission is available

 You can use with to make a judgment


0011 1100 (View, Review, Detail, Delete), determine whether there is a view (0010 0000) permission, and then determine whether there is a create (0000 0001) permission

0011 1100
0010 0000
---------
0010 0000


0011 1100
0000 0001
---------
0000 0000



As you can see from the example above, it’s really easy to use bitwise arithmetic, so let’s take a look at the use of bitwise arithmetic in React.

 Bitwise operations in React

  •  fiber flags
  •  lane model
  • content

 fiber flags


In React, flags, which are used to mark fiber operations, use binary:

export const NoFlags = /*                      */ 0b000000000000000000000000000;
export const PerformedWork = /*                */ 0b000000000000000000000000001;
export const Placement = /*                    */ 0b000000000000000000000000010;
export const DidCapture = /*                   */ 0b000000000000000000010000000;
export const Hydrating = /*                    */ 0b000000000000001000000000000;
// ...


These flags are used to mark the fiber state.


The reason why I want to specifically abstract away the state of the fiber is that this operation is very efficient. The operations on a fiber may include adding, deleting, or modifying, but instead of doing them directly, I put a flag on the fiber and then unify the operations on the flagged fiber later in the process.


The problem of a fiber having multiple flag tokens can be well solved by bitwise arithmetic, which facilitates the merging of multiple states

const NoFlags = 0b00000000000000000000000000;
const PerformedWork =0b00000000000000000000000001;
const Placement =  0b00000000000000000000000010;
const Update = 0b00000000000000000000000100;

let flag = NoFlags

flag = flag | PerformedWork | Update

if(flag & PerformedWork){
    console.log(' PerformedWork')
}

if(flag & Update){
    console.log(' Update')
}


if(flag & Placement){
    console.log(' Placement')
}

 lane model


The lane model is also a prioritization mechanism that allows for finer-grained control of tasks than the Scheduler.

export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;

export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;

export const InputContinuousHydrationLane: Lane = /*    */ 0b0000000000000000000000000000010;
export const InputContinuousLane: Lane = /*             */ 0b0000000000000000000000000000100;
// ...

 For example, in the React source code, there is a piece of code that looks like this:

function getHighestPriorityLanes(lanes) {
  switch (getHighestPriorityLane(lanes)) {
    case SyncLane:
      return SyncLane;
    case InputContinuousHydrationLane:
      return InputContinuousHydrationLane;
    case InputContinuousLane:
      return InputContinuousLane;
      // ...
      return lanes;
  }
}

// 0000 0001
// 0000 0010
// 0010 0000
// 0010 0011 ----> getHighestPriorityLane -----> 0000 0001

export function getHighestPriorityLane(lanes) {
  return lanes & -lanes;
}

 Let’s say we merge the two lanes now.

const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;
const InputContinuousLane: Lane = /*             */ 0b0000000000000000000000000000100;


The merge out is a lanes, and the merge out is as follows:

0b0000000000000000000000000000001
0b0000000000000000000000000000100
---------------------------------
0b0000000000000000000000000000101


0b00000000000000000000000000000000101 is our lanes, take the next negative value

-lanes = 0b1111111111111111111111111111011


The last step is to do an & operation with the lanes themselves:

0b0000000000000000000000000000101
0b1111111111111111111111111111011
---------------------------------
0b0000000000000000000000000000001


After the & operation, the highest priority lane is separated.

 Inside the React source code, there are multiple contexts:

export const NoContext = /*             */ 0b000;
const BatchedContext = /*               */ 0b001;
export const RenderContext = /*         */ 0b010;
export const CommitContext = /*         */ 0b100;


When the execution process reaches the render phase, the context is switched to the RenderContext next.

let executionContext = NoContext; 
executionContext |= RenderContext;


When the method is executed, a judgment is made as to which context it is currently in

(executionContext & RenderContext) !== NoContext

(executionContext & CommitContext) !== NoContext

 To leave a particular context

executionContext &= ~RenderContext;
(executionContext & CommitContext) !== NoContext

By hbb

Leave a Reply

Your email address will not be published. Required fields are marked *