When I went to work two days ago, Xiaoye suddenly sent me a message: Brother, what do you mean by these two pieces of code?
At first glance, this code feels familiar and unfamiliar. I seem to have seen it somewhere, but it seems to be rarely used in normal times.
water and thought for 3s calmly: Hey, isn't this the 160bf0e7dad6ec bit operator? I have studied it in university before, and I have seen the source code of
react
Xiaoye: Brother, can you tell me what this is?
Me: No problem, wait for me to sort it out~
What is bit operation?
In simple terms, bit operations are operations based on the binary representation of an integer. It processes every bit directly, which is a very low-level operation. The advantage is that it is extremely fast, but the disadvantage is that it is not intuitive.
You might ask: What is binary?
Haha, in fact, if you are not a classmate, it is normal to be a little unfamiliar with binary. Let me briefly introduce the binary.
Binary
The commonly used numbers such as 2, 8, 16 are represented in decimal, and the basis of bit operation is binary. That is, humans use decimal system, and machines use binary system. To understand bit operations in depth, you need to understand the conversion method and corresponding relationship between decimal system and binary system.
Decimal to Binary
Converting a decimal integer to a binary integer uses to divide by 2 to take the remainder, and the reverse order method. The specific method is: Divide the decimal integer by 2 to get a quotient and remainder; then divide the quotient with 2 to get a quotient and remainder. Continue this way until the quotient is less than 1, and then use the first obtained remainder as binary The low-significant bits of the number, and the remainder obtained as the high-significant bits of the binary number, are arranged in sequence.
Take the decimal number 156 as an example:
Binary to decimal
Before the decimal point or the integer from right to left, multiply each binary number by the corresponding power of 2 and increase it; after the decimal point, multiply it by the corresponding negative power of two from left to right and decrease it.
Here is 1011.01 as an example:
After introducing the conversion between binary and decimal, let's take a look at several bit operators frequently used js
7 bit operators commonly used in JS
There are 7 basic bit operations, namely
bitwise AND (AND) &
bitwise OR (OR) |
bitwise exclusive OR (XOR) ^
Bitwise Not (NOT) ~
Left shift <<
signed right shift >>
unsigned right shift >>>
Here is a table to summarize the introduction of the above 7 operators:
Operator | usage | description | ||
---|---|---|---|---|
bitwise AND (AND) & | a & b | For each bit, the result is 1 only when the corresponding bits of both operands are 1, otherwise it is 0. | ||
`Bitwise OR (OR) | ` | a | b | For each bit position, when the corresponding bit position of the two operands has at least one 1, the result is 1, otherwise it is 0. |
bitwise exclusive OR (XOR) ^ | a ^ b | For each bit position, when the corresponding bit position of the two operands has and only one 1, the result is 1, otherwise it is 0. | ||
Bitwise Not (NOT) ~ | ~a | Reverse the bits of the operand, that is, 0 becomes 1 and 1 becomes 0. | ||
Left shift << | a << b | Shift the binary form of a to the left by b (< 32) bits, and fill it with 0s on the right. | ||
signed right shift >> | a >> b | Shift the binary representation of a to the right by b (< 32) bits, and discard the bits that are shifted out. | ||
unsigned right shift >>> | a >>> b | Shift the binary representation of a to the right by b (< 32) bits, discard the bits that are shifted out, and fill it with 0 on the left. |
bitwise AND (AND) &
&
operator (bit AND) is used to compare two binary operands bit by bit. If the corresponding bits are all 1, the result is 1, and if any bit is 0, the result is 0.
bitwise OR (OR) |
|
operator (bitwise OR) is used to compare two binary operands bit by bit. As long as one of the two corresponding bits is 1, it is 1, otherwise it is 0.
bitwise exclusive OR (XOR) ^
^
operator (bitwise exclusive OR) is used to compare two binary operands bit by bit. It is 1 only when the two corresponding bits are different.
bitwise not (NOT) ~
~
operator (bit NOT) is used to compare two binary operands bit by bit. To reverse the position, 1 becomes 0, and 0 becomes 1.
Here is a little troublesome, 11111111 11111111 11111111 11111110
explain: 1's complement binary representation: 060bf0e7dade50. Since the first bit (sign bit) is 1, this number is a negative number. JavaScript uses complement form to represent negative numbers, that is, you need to subtract 1 from this number, take the inverse again, and add the negative sign to get the decimal value corresponding to this negative number.
11111111 11111111 11111111 11111101
minus 1 is: 060bf0e7dade71.
00000000 00000000 00000000 00000010
code: 060bf0e7dc188b. Plus the sign bit -
. Finally, the bitwise -2
1 is 060bf0e7dc1896.
The negative number of a binary number is to take the complement of the binary number, and then +1. Binary number, the highest bit is 0 for positive number, the highest bit is 1 for negative number. ~
bitwise non-operation is actually the process of complementing the code, which is the inverse process of finding the negative number of the value mentioned above, so it can be simply understood as the value minus 1 after taking the negative value.
There is actually a little trick here: own negated value is equal to -1.
Left shift <<
<<
operator (left shift) means to move the specified binary to the left by the specified number of bits.
signed right shift >>
>>
operator (shift right) means to move the specified binary to the right by the specified number of bits.
On MDN
you can see:
The last sentence of this sentence mentions "sign-propagating"
. The Chinese translation means symbol propagation. Why do you say this: we know that the computer stores numbers in binary, and the first bit of the leftmost in the binary is called the
sign bit, so this It is obvious that after shifting by 2 digits to the right, the leftmost 2 digits are missing. Then the digits should be filled in. What should be filled in?
sign bit of 160bf0e7dc1b10, I will fill it in. Since the new leftmost bit is always the same as before, the sign bit has not been changed. So it is called "symbol propagation".
unsigned right shift >>>
Many students may be >>>
and >>
. Similarly, let’s look at the MDN
of unsigned right shift >>> on 060bf0e7dc1d55:
Similarly, there is a core word: zero-fill right shift
. The translation is zero-padding. This is more obvious. After shifting to the right, no matter what your sign bit is, I only fill in 0.
Here you can get a conclusion: For non-negative numbers, signed right shift and unsigned right shift always return the same result.
At this point, the introduction of the 7 bit operators commonly used in JS is almost complete. Let's take a look at the usage scenarios of the bitwise operator React
in (
after all, this is the first time I have seen with bitwise operators in actual business scenarios)
Usage scenarios in React
EffectTag
We know that each React element corresponds to a
fiber object. A fiber object is usually a basic unit
work
// packages/react-reconciler/src/ReactFiber.js
// A Fiber is work on a Component that needs to be done or was done. There can
// be more than one per component.
export type Fiber = {
// 标识 fiber 类型的标签
tag: WorkTag,
// 指向父节点
return: Fiber | null,
// 指向子节点
child: Fiber | null,
// 指向兄弟节点
sibling: Fiber | null,
// 在开始执行时设置 props 值
pendingProps: any,
// 在结束时设置的 props 值
memoizedProps: any,
// 当前 state
memoizedState: any,
// Effect 类型,详情查看以下 effectTag
effectTag: SideEffectTag,
// effect 节点指针,指向下一个 effect
nextEffect: Fiber | null,
// effect list 是单向链表,第一个 effect
firstEffect: Fiber | null,
// effect list 是单向链表,最后一个 effect
lastEffect: Fiber | null,
// work 的过期时间,可用于标识一个 work 优先级顺序
expirationTime: ExpirationTime,
};
Each fiber
node has a effectTag
value associated with it.
We call some work
that cannot be render
stage as side effects. React
lists various possible side effects, as shown below:
// packages/shared/ReactSideEffectTags.js
export type SideEffectTag = number;
// Don't change these two values. They're used by React Dev Tools.
export const NoEffect = /* */ 0b000000000000;
export const PerformedWork = /* */ 0b000000000001;
// You can change the rest (and add more).
export const Placement = /* */ 0b000000000010;
export const Update = /* */ 0b000000000100;
export const PlacementAndUpdate = /* */ 0b000000000110;
export const Deletion = /* */ 0b000000001000;
export const ContentReset = /* */ 0b000000010000;
export const Callback = /* */ 0b000000100000;
export const DidCapture = /* */ 0b000001000000;
export const Ref = /* */ 0b000010000000;
export const Snapshot = /* */ 0b000100000000;
export const Passive = /* */ 0b001000000000;
// Passive & Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /* */ 0b001110100100;
// Union of all host effects
export const HostEffectMask = /* */ 0b001111111111;
export const Incomplete = /* */ 0b010000000000;
export const ShouldCapture = /* */ 0b100000000000;
It can be seen that most of the values have only one bit as 1, and the other bits are all 0.
0bxxx
is a representation method of native binary literals
Here is a scenario effectTag
(workInProgress.effectTag & DidCapture) !== NoEffect
Here is actually the AND on the binary: we take
Update
and DidCapture
to perform the &
operation, then the result obtained is obvious, all bits are 0.
So the &
operation here is used to judge whether a certain attribute is contained in a certain variable. For example, here is to determine whether workInProgress.effectTag
contains the attribute DidCapture
There are many more corresponding bit operation scenarios in the react source code, so I won't explain them one by one here. Let's take a look at the scenarios where bit operations are more commonly used in actual business development.
The magical use of bit operators in JS
Switch variable 0 or 1
Usually we do some variable state switching, most of which we will write like this:
if (flag) {
flag = 0;
} else {
flag = 1;
}
Or abbreviated as:
flag = flag ? 0 : 1;
If it is implemented by bit operations:
toggle ^= 1;
Does it feel simple~
Use & operator to determine the parity of a number
Compared with the way of taking the remainder of 2, this way is also more concise:
// 偶数 & 1 = 0
// 奇数 & 1 = 1
console.log(2 & 1) // 0
console.log(3 & 1) // 1
Exchange two numbers (extra variables cannot be used)
The most intuitive way to exchange the values of two integers is to use a temporary variable:
let a = 5,
b = 6
// 交换a, b的值
let c = a
a = b
b = c
It is now required that no additional variables or content space can be used to exchange the values of two integers. At this time, you have to use bit arithmetic and use exclusive or to achieve this goal:
let a = 5,
b = 6;
a = a ^ b; // 3
b = a ^ b; // 5
a = a ^ b; // 6
If you don't understand it, let's analyze it separately.
First: a = a ^ b
, that is, a = 0101 ^ 0110 = 0011 = 3
;
The second step: b = a ^ b
, that is, b = 0011 ^ 0110 = 0101 = 5
;
Finally: a = a ^ b
, which is a = 0011 ^ 0101 = 0110 = 6
.
So far, the exchange of two integer values is completed without using additional variables.
Application of powers of 2
leetcode
on this piece, let’s take a look at it together:
Power of 2
The more conventional method is to keep dividing by 2 to determine whether the final result is 1, which is to use the recursive method.
/**
* @param {number} n
* @return {boolean}
*/
var isPowerOfTwo = function(n) {
if (n < 1){
return false;
}
if (n == 1) {
return true;
}
if (n % 2 > 0) {
return false;
}
return isPowerOfTwo(n / 2);
};
Let's consider if there is a faster solution: observe 2^0, 2^1, 2^2...2^n, their binary representations are 1, 10, 100, 1000, 10000... ...
To determine whether a number is 2 to the power of n, that is, to determine whether there is only one bit in the binary representation that is 1 and the position of the first bit. For example, n=00010000, then n-1=00001111, which is n&(n-1)==0
From this we can judge it!
/**
* @param {number} n
* @return {boolean}
*/
var isPowerOfTwo = function(n) {
return n > 0 && (n & (n-1)) === 0
};
Number of bits 1
Here is a little trick that can be easily figured out. That is, n & (n-1) can eliminate the last 1 n.
If this bit of better understanding, then I believe you must be above this skill
very familiar 🙈
In this way, we can continue to n = n & (n - 1)
until n === 0
, which means that there is no 1 anymore. Go through count
to count~
/**
* @param {number} n - a positive integer
* @return {number}
*/
var hammingWeight = function(n) {
let count = 0;
while (n !== 0) {
// n & (n - 1) 可以消除 n 最后的一个1
n = n & (n-1)
count++
}
return count
};
Authorization system design
It is a very common behavior to control the operation authority of different users in the background management system. Usually we will use an array to represent the operation permissions owned by a certain user:
roles: ["admin", "editor"],
Imagine if we use bit operations to design this permission system:
- Administrator (first level authority): 0b100
- Development (second level permissions): 0b010
- Operation (three levels of authority): 0b001
If operation A can only be operated by administrators and developers, then this authority value is expressed as 060bf0e7dc2b7f, which 6 = 0b110
&
: 6 & 4 = 0b110 & 0b100 = 4
, the value obtained is not 0, so it is considered to have authority. In the same way, the development authority &
6 & 2 = 2
not 0, and the operation authority &
6 & 1 = 0
, so there is no authority for operation.
The advantage of this is that we can use a number instead of an array to represent the permission set of a certain operation, and it is also very convenient when making permission judgments.
to sum up
This article has done a more systematic explanation bit operation. In fact, in actual development, there are many students who
bit count. But in some scenarios, the reasonable use of
bit operations can also solve many practical problems.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。