什么是位
介绍位之前,要先介绍一下什么是 二进制
我们一般数学中用到的都是十进制 ,当碰见9 的时候,再往前大1,那么就要进位,变为10
那么 二进制 也是同样的道理,只有0 / 1两种状态,遇到1 的时候,再往前大1,就要进位
📝 为什么 计算机 采用
二进制
计算机是由逻辑电路组成,逻辑电路通常只有两个状态,开关的接通与断开,这两种状态正好可以用“1”和“0”表示
抗干扰能力强,可靠性高:二进制中只使用0和1两个数字,传输和处理时不易出错,因而可以保障计算机具有很高的可靠性
与十进制数相比,二进制数的运算规则要简单得多,这不仅可以使运算器的结构得到简化,而且有利于提高运算速度。
计算机对二进行的运算( |、&、^、<< 、 >> ) 都是叫位运算
优势就是处理数据速度很快, 缺点是对于程序员来说有点不那么直观
🚀 位运算符
将 位运算符之前,先介绍一下 js自带的 二进制 / 十进制 的转换方法
十进制转二进制
var num = 100;
num.toString(2) // '1100100'
二进制转十进制
var num = 1100100;
parseInt(num,2);
1. << (按位左移)
左移 将第一个操作数 向 左移动指定位数,左边超出的位数将会被清除,右边将会补零。
9 (十进制): 001001 (二进制)
--------------------------------
9 << 2 (十进制): 100100 (二进制) = 36 (十进制)
// 左移 2位
1 (十进制): 000001 (二进制)
--------------------------------
1 << 2 (十进制): 000100 (二进制) = 4 (十进制)
// 左移 3位
1 (十进制): 000001 (二进制)
--------------------------------
1 << 3 (十进制): 001000 (二进制) = 8 (十进制)
可以看出9换算成 二进制 是 001001
根据定义 ==== 第一个1 移动 2位, 001001 ==> 100100
同样的右移(>>)就是向右移动指定位数
2. | (按位或)
或 只要有一个为 1,则为 1
举例
5 | 1 = 5
5表示为 二进制 为 0101
1表示为 二进制 为 0001
5: 0 1 0 1
1: 0 0 0 1
———————————
5: 0 1 0 1
3. & (按位与)
与 只要两个同时为 1,则为 1
举例
5 & 1 = 1
5 & 2 = 0
5: 0 1 0 1
1: 0 0 0 1
———————————
1: 0 0 0 1
5: 0 1 0 1
2: 0 0 1 0
———————————
0: 0 0 0 0
4. ^ (按位异或)
按位异或 两个操作数有且仅有一个对应的二进制位为 1 时,该位的结果值为 1。
举例
5 & 1 = 4
5 & 2 = 7
5: 0 1 0 1
1: 0 0 0 1
———————————
4: 0 1 0 0
5: 0 1 0 1
2: 0 0 1 0
———————————
7: 0 1 1 1
说了这么多,到底应该怎么在实际场景中使用呢?
🥁 实际使用
vue
enum ShapeFlags {
ELEMENT = 1, // 虚拟节点是一个元素
FUNCTIONAL_COMPONENT = 1 << 1, // 函数式组件
STATEFUL_COMPONENT = 1 << 2, // 普通组件
TEXT_CHILDREN = 1 << 3, // 儿子是文本的
ARRAY_CHILDREN = 1 << 4, // 儿子是数组
SLOTS_CHILDREN = 1 << 5, // 插槽
TELEPORT = 1 << 6,
SUSPENSE = 1 << 7,
COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
COMPONENT_KEPT_ALIVE = 1 << 9,
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT,
}
在 vue 中使用 左移来标注 element 的类型,可以发现有个特点 就都是 1的左移操作
为什么这样设计呢 ?
其实这样有个显著的优点是: 换成2进制只有一位有 1
1 = 000001
1 << 2 = 000100 (二进制) = 4 (十进制)
1 << 3 = 001000 (二进制) = 8 (十进制)
1 << 4 = 010000 (二进制) = 16 (十进制)
那么只有只有 1 个 1 有什么好处呢?🧐
好处 是便于 判断 / 扩大
怎么判断呢?🧐
以 COMPONENT 举例说明
ShapeFlags.STATEFUL_COMPONENT = 1 << 2 = 00100
ShapeFlags.FUNCTIONAL_COMPONENT = 1 << 1 = 00010
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT,
执行按位或 (在 | 按位或中,只要其中 一个位 是 1 ,则为1)
1 << 2 ==> 4: 0 1 0 0
1 << 1 ==> 2: 0 0 1 0
——————————————————————
6: 0 1 1 0
最终COMPONENT的二进制结果是0 1 1 0
怎么使用COMPONENT进行判断呢?🧐
使用 &(按位与) 进行判断是否包含 (按位与 必须是 两位都为1,才能为 1)
以 COMPONENT 是否包含FUNCTIONAL_COMPONENT和TEXT_CHILDREN举例说明
console.log(ShapeFlags.COMPONENT & ShapeFlags.FUNCTIONAL_COMPONENT); // 2
console.log(ShapeFlags.COMPONENT & ShapeFlags.TEXT_CHILDREN); // 0
ShapeFlags.COMPONENT & ShapeFlags.FUNCTIONAL_COMPONENT
6: 0 1 1 0
2: 0 0 1 0
——————————————————————
2: 0 0 1 0
ShapeFlags.COMPONENT & ShapeFlags.TEXT_CHILDREN
6: 0 1 1 0
8: 1 0 0 0
——————————————————————
0: 0 0 0 0
只要判断 按位与之后 的结果 是否> 0 ,就可以判断 是否包含对应的elemnetType
✈️ 总结: 按位 或 扩大查找范围, 按位与 进行判断是否包含
leetCode
给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间
示例:
示例 1:
输入:nums = [2,2,1]
输出:1
示例 2 :
输入:nums = [4,1,2,1,2]
输出:4
示例 3 :
输入:nums = [1]
输出:1
这道题有很多种解法,第一时间想到的应该是 遍历,形成一个[{value:对应 value出现的次数}]的这种结构
但是题目中有一句很重要某个元素只出现一次以外,其余每个元素均出现两次, 所以有更简单的算法,那就是使用异或操作符
异或是 只要有操作位中 有且只能有1位 是 1,才为 1
举例 [6,8,6]
6: 0 1 1 0
8: 1 0 0 0
———————————————
14: 1 1 1 0
6 : 0 1 1 0
———————————————
8: 1 0 0 0
这样把 数字8 给筛选出来了
var singleNumber = function(nums) {
let num = nums[0];
for(let i = 1;i<nums.length;i++){
num = num ^ nums[i] } return num
};
❤️ 总结
位运算其实在前端用的不算太多,但是由于是操作的二进制数据,所以有一些独特的优势,在权限管理方面,可以参考Vue的这种做法,不使用字符串 admin或者是 normal来进行判断,使用二进制
不过还是那句话,只要项目和人能跑一个就行, 如果你对自己的代码有更高的要求的话,还是要多学习一些最佳实践🎉