「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」
前言
这篇文章你可以学习到js的运算符到一些特性,还有一些实际的使用场景
分为初级 〉 中级 〉进阶
如果觉得不错或者哪里可以补充的,可以评论留言
初级
类型转换
string类型转换nubmer类型
数字转类型有些同学可能会这样写
//before
const a = '123'
Number(a) // 123
其实可以用*1 或者 + 来做隐式转换
//after
const a = '123'
console.log(+a) // 123
console.log(a * 1) //123
用+ 来隐式转换的其他示例
'123' // 123
'ds' // NaN
'' // 0
null // 0
undefined // NaN
{ valueOf: ()=>'3' } // 3
number类型转string类型
//before
const a = 123
String(a) //'123'
可以用 + ‘ ’ 来做隐式转换
//after
const a = 123
console.log(a + '') // '123'
转Boolean类型
双!!
直接转成布尔值,真值会转成true,假值会转成false
//before
Boolean(1) //true
//after
!!123 //true
使用 && 作逻辑判断
&&
除了用来做条件判断,因为其返回值特性(如果第一个为真值,返回后面那个值的结果,否则直接返回前值结果,后面的值不会继续求值),也可以运用在逻辑判断
//条件判断
if(a === 1 && b ===2 ) {
//todo
}
其实当if内的逻辑不复杂的时候,也可以用来做代替if
const arr - [1, 2, 3]
const index = arr.indexOf(1)
//before
if(index === -1) {
arr.splice(index, 1)
}
//after
index === -1 && arr.splice(index, 1)
//其实这个还可以再用位运算符 `~ `简化一点,继续向下看到位运算符就能看到。
如果是两个值中取一个值,而且也可以进行这样的简写(不过这是ES2020的语法,要注意兼容)
//当a是真值则返回b,否则返回a
a &&= b
//等同于
a = a && b
使用 || 做变量赋值
|| 除了用来做条件判断,因为其返回值特性(第一个值为真值,则返回第一个值的结果, 如果第一个为假值,则继续往后查结果,直到返回真值,返回真值后,如果后面还有值的话,不会求值),也可以运用在变量赋值判断
//条件判断
if(a === 1 || b === 2) {
//todo
}
//变量赋值
const a = null || 1
console.log(a) // 1
当用来给值当默认值的时候,也可以进行这样的简写,不过这是ES2020的写法,要注意兼容
//当box标签有值则使用这个值,没值使用默认值
document.getElementById('box').innerHTML ||= '<div>我是默认文本</div>'
//等同于
document.getElementById('box').innerHTML = document.getElementById('box').innerHTML || '<div>我是默认文本</div>'
可选链接运算符 (?.)
可选链接运算符 ?.
处于ES2020提案的第4阶段,要用的话要注意babel兼容
当我们使用了?.
时,如果这个值为假值,会停止执行,直接返回undefined
平时我们开发如果如果后端的数据有个字段是数组,我们要操作它,以防万一我们一般会进行数组判断,防止因为报错而影响后续执行
//before
const data = []
if(data && data.length) {
//todo
}
//after
if(data?.length) {
//todo
}
或者对象的值可能为空时,这样调用方法就会报错,也可以用这个判断
const res = obj?.name()
//这样不会执行,也不会报错,直接返回undefined
后面也可以跟动态变量名和数组取值
const res = datas?.data?.[arrName]?.[0]
空值合并运算符(??)
空值合并运算符(??
)是一个逻辑操作符,当左侧的操作数为null
或者undefined
时,才会返回右侧操作数结果,否则返回左侧结果(不过这是ES2020的语法,要注意兼容)
??
和||
有点像,但是||
判断的是假值,??
是判断null和undefined
//真值
const a = 1 ?? '默认值'
console.log(a) // 1
//假值
const a = '' ?? '默认值'
console.log(a) // ''
//null
const a = null ?? '默认值'
console.log(a) // '默认值'
//undefined
const a = undefined ?? '默认值'
console.log(a) // '默认值'
也可以进行简写
let a = 1
a ??= '默认值'
//等同于
a = a ?? '默认值'
console.log(a) // 1
中级
按位非运算符 (~)
简单说明下按位非运算符 ~
按位非运算符 ~
是二进制的取反符号,取二进制的反码,对位为0则变1,1则变0,
有一个规律,就是~正数的话,结果等于-(x + 1)
下面32bit的二进制举例
1 = 0000 0000 0000 0000 0000 0000 0000 0001
~1 = 1111 1111 1111 1111 1111 1111 1111 1110
下面来说下这个符号用的场景
取整
可以使用双位操作符~~
来替代正数的 Math.floor( )
,替代负数的Math.ceil( )
当值为正数时
const num = 6.6
//before
Math.floor(num) // 6
//after
~~num // 6
当值为负数时
const num = -6.6
//before
Math.ceil(num) // -6
//after
~~num // -6
判断是否等于-1
当~-1
时候的值为0,可以用来判断indexOf的值
const arr = [1, 2, 3, 4, 5]
const index = arr.indexOf(1)
//这里index值为0,~index的值为-1,-1是真值
~index && arr.splice(index, 1)
console.log(arr); //[2, 3, 4, 5]
按位或运算符 (|)
简单说明下按位或运算符 |
它需要至少需要两个操作数做对比,对位的数只有有一个是1,则返回1,否则返回0
我们用10 和 18 的 举例
10 = 0000 0000 0000 0000 0000 0000 0000 1010
18 = 0000 0000 0000 0000 0000 0000 0001 0010
---------------------------------------------
10 | 18 = 0000 0000 0000 0000 0000 0000 0001 1010 = 26
下面来说下按位异或运算符 ^
可以使用的场景
取整
可以用 位或运算符 |
进行取整
6.66 | 0 //6
-6.66 | 0 //-6
代替Math.round()
//正数
const a1 = 1.4
const a2 = 1.6
a1 + 0.5 | 0 // 1
a2 + 0.5 | 0 // 2
//负数
const b1 = -1.4
const b2 = -1.6
b1 + 0.5 | 0 // -0
b2 + 0.5 | 0 // -1
按位与运算符(&)
使用按位与运算符 &
实际上是操作数
同为比对的结果。
当对位的数有一个是0的时候,返回0,否则为1
10 = 0000 0000 0000 0000 0000 0000 0000 1010
1 = 0000 0000 0000 0000 0000 0000 0000 0001
---------------------------------------------
10 | 1 = 0000 0000 0000 0000 0000 0000 000 0000 = 0
下面来说下按位与运算符 &
可以使用的场景
判断奇偶数
8 & 1 // 0
7 & 1 // 1
// 0是假值,8 & 1 直接用来判断是奇数,如果是想直接判断偶数,则用num & 2
if(8 & 1) {
//如果是奇数执行的逻辑
}
判断是否为2的整数幂
const a = 20;
const b = 32;
a & (a - 1) // 16 a不是2的整数幂
b & (b - 1) // 0 b是2的整数幂
按位异或运算符 (^)
使用按位异或符 ^
实际上是操作数
同为比对的结果。
当比对位相加为1时返回1,否则返回0
下面拿32位(bit)举例
10 = 0000 0000 0000 0000 0000 0000 0000 1010
18 = 0000 0000 0000 0000 0000 0000 0001 0010
--------------------------------------------
10 ^ 18 = 0000 0000 0000 0000 0000 0000 0001 1000 = 24
下面来说下位异或运算符 ^
可以使用的场景
切换0和1
在做开关类的功能时可能会用变量保存0和1来判断,其实可以用^
切换
//before
let toggle = 0
if(toggle) {
toggle = 1
} else {
toggle = 0
}
//或者用三元运算符
toggle = toggle ? 0 : 1
用^
的方式看起来更简单
let toggle = 0
toggle ^= 1
console.log(toggle) // 1
判断两数符号是否相同
const a = 1
const b = 2
const c = -1
const d = -2
(a ^ b) >= 0 // true
(a ^ c) >= 0 // false
(c ^ d) >= 0 // true
判断整数部分是否相等
因为位运算符操作的是正数
//都是正数
const a = 1
const b = 1
a ^ b // 0
//小数点
const a = 1.1
const b = 1.6
a ^ b // 0
完成值交换
我们也可以使用按位异或来进行两个变量的值交换
let a = 1
let b = 2
a ^= b
b ^= a
a ^= b
console.log(a) // 2
console.log(b) // 1
不过这种其实有点麻烦,其实用ES6的解构更方便
let a = 1;
let b = 2;
[a, b] = [b, a]
console.log(a) // 2
console.log(b) // 1
进阶
左移(<<)
左移用符号 <<
来表示,正如它的名字,即将数值的二进制码按照指定的位数向左移动,然后空位填充0,符号位不变(最高位是符号位)
2 = 0000 0000 0000 0000 0000 0000 0000 0010
//我们把2 左移 5位
2 << 5 = 0000 0000 0000 0000 0000 0000 0100 0000 = 64
左移也可以用来取整
1.66 << 0 // 1
右移(>>)
有符号右移用符号 >>
来表示,即将数值的二进制码按照指定的位数向右移动,符号位不变,空位填充0,它和左移相反
2 = 0000 0000 0000 0000 0000 0000 0000 0010
//我们把2 右移 2位
2 >> 5 = 0000 0000 0000 0000 0000 0000 0000 0000 = 0
//再举个例子,我们把 64 右移5位
64 = 0000 0000 0000 0000 0000 0000 0100 0000
64 >> 5 = 0000 0000 0000 0000 0000 0000 0000 0010 = 2
无符号右移 (>>>)
“>>>”运算符执行无符号右移位运算。它把无符号的 32 位整数所有数位整体右移。对于无符号数或正数右移运算,无符号右移与有符号右移运算的结果是相同的。
//正数和>> 结果一样
10 >> 2 // 2
10 >>> 2 // 2
//对于负数来说,无符号右移将使用 0 来填充所有的空位,同时会把负数作为正数来处理,所得结果会非常大所以,使用无符号右移运算符时要特别小心,避免意外错误。
-1 >>> 0 // 4294967295
16进制颜色值和RGB颜色值相互转换
前面我们提起过左移<<
和右移>>
,现在展示一下使用场景
16进制颜色值转RGB:
function hexToRGB(hex){
var hex = hex.replace("#","0x"),
r = hex >> 16,
g = hex >> 8 & 0xff,
b = hex & 0xff;
return "rgb("+r+","+g+","+b+")";
}
hexToRGB("#ffffff") //rgb(255,255,255)
RGB转16进制颜色值:
function RGBToHex(rgb){
var rgbArr = rgb.split(/[^\d]+/),
color = rgbArr[1]<<16 | rgbArr[2]<<8 | rgbArr[3];
return "#"+color.toString(16);
}
RGBToHex("rgb(255,255,255)") // #ffffff
使用位运算符做权限控制
分配权限
上面我们说了左移<<
是把二进制码移动对应位数
// 1 的二进制为 00000001
1 << 0 // 00000001
1 << 1 // 00000010
1 << 2 // 00000100
1 << 3 // 00001000
1 << 4 // 00010000
1 << 5 // 00100000
...
大家会发现,每个数里都有且只有一个1,并且每个位置都不一样,这时如果把他们组合在一起的话,其实满足了组合的唯一性。
我们拿 vue-next 的patchFlags.ts文件中的这一段举个栗子,这段是 VisualDOM
中对 vnode
的类型标记,作用是在更新 DOM树
的时候会根据 vnode
的类型来使用不同的更新策略,我们看看类型的定义
* Patch flags can be combined using the | bitwise operator and can be checked
* using the & operator, e.g.
*
* ```js
* const flag = TEXT | CLASS
* if (flag & TEXT) { ... }
* ```
*
* Check the `patchElement` function in '../../runtime-core/src/renderer.ts' to see how the
* flags are handled during diff.
*/
export const enum PatchFlags {
TEXT = 1, // 1 << 0
CLASS = 1 << 1,
STYLE = 1 << 2,
PROPS = 1 << 3,
FULL_PROPS = 1 << 4,
HYDRATE_EVENTS = 1 << 5,
STABLE_FRAGMENT = 1 << 6,
KEYED_FRAGMENT = 1 << 7,
UNKEYED_FRAGMENT = 1 << 8,
NEED_PATCH = 1 << 9,
DYNAMIC_SLOTS = 1 << 10,
// SPECIAL FLAGS -------------------------------------------------------------
// Special flags are negative integers. They are never matched against using
// bitwise operators (bitwise matching should only happen in branches where
// patchFlag > 0), and are mutually exclusive. When checking for a special
// flag, simply check patchFlag === FLAG.
HOISTED = -1,
BAIL = -2
}
这里作者用 <<
定义了除了特性的类型外的 11 种类型,每一种都是通过左移得到的,作者在上面注释已经给了提示,使用 |
来权限赋予, 用&
来做权限校验
权限赋予
上面我们说过|
是对操作数们做比对,对位的位数只有有一个是1,则返回1,否则返回0
我们创建一个权限看看
const permission = 0 //初始化无权限角色
const permission1 = permission | TEXT | CLASS | PROPS
---------------------------------------------
//根据上面文件的定义
TEXT = 0000 0000 0000 0000 0000 0000 0000 0001
CLASS = 0000 0000 0000 0000 0000 0000 0000 0010
PROPS = 0000 0000 0000 0000 0000 0000 0000 1000
permission1 = permission | TEXT | CLASS | PROPS = 0000 0000 0000 0000 0000 0000 0000 1011 = 8
这时赋予了一个permission1
的权限,可以操控TEXT
, CLASS
, PROPS
权限校验
上面我们说过&
是对操作数们做比对,当对位的数有一个是0的时候,返回0,否则返回为1。
现在进行权限校验
//当有权限的时候
permission1 & TEXT
------------------------------------------------
permission1 = 0000 0000 0000 0000 0000 0000 0000 1011
TEXT = 0000 0000 0000 0000 0000 0000 0000 0001
permission1 & TEXT = 0000 0000 0000 0000 0000 0000 0000 0001 = 1 = true
//当没有权限的时候
permission1 & STYLE
------------------------------------------------
permission1 = 0000 0000 0000 0000 0000 0000 0000 1011
STYLE = 0000 0000 0000 0000 0000 0000 0000 0100
permission1 & STYLE = 0000 0000 0000 0000 0000 0000 0000 0000 = 0 = false
删除权限
删除权限的本质其实是将指定位置上的 1 重置为 0
我们可以使用^
, ^
规则是当比对位相加为1时返回1,否则返回0
permission1 ^ TEXT
-------------------------------------------
permission1 = 0000 0000 0000 0000 0000 0000 0000 1011
TEXT = 0000 0000 0000 0000 0000 0000 0000 0001
permission1 ^ TEXT = 0000 0000 0000 0000 0000 0000 0000 1010
不过这种有缺点,就是如果没有这个权限的话,用^
会增加权限
可以删除前进行判断,或者用&~
来删除
//当删除权限存在时
permission1 & (~TEXT)
-------------------------------------------
permission1 = 0000 0000 0000 0000 0000 0000 0000 1011
~TEXT = 1111 1111 1111 1111 1111 1111 1111 1110
permission1 & (~TEXT) = 0000 0000 0000 0000 0000 0000 0000 1010
//当删除的权限不存在的时候
permission1 & (~STYLE)
--------------------------------------------
permission1 = 0000 0000 0000 0000 0000 0000 0000 1011
~STYLE = 1111 1111 1111 1111 1111 1111 1111 1011
permission1 & (~STYLE) = 0000 0000 0000 0000 0000 0000 0000 1011