1.数值分割符 _
ES2021 引入了数值分割符 _,在数值组之间提供分隔,使一个长数值读起来更容易。Chrome 已经供了对数值分割符的支持,可以在浏览器里试起来。
let number = 100_0000_0000_0000 // 0太多了不用数值分割符眼睛看花了
console.log(number) // 输出 100000000000000
此外,十进制的小数部分也可以使用数值分割符,二进制、十六进制里也可以使用数值分割符。
0x11_1 === 0x111 // true 十六进制
0.11_1 === 0.111 // true 十进制的小数
0b11_1 === 0b111 // true 二进制
2.逗号运算符 ,
什么,逗号也可以是运算符吗?是的,曾经看到这样一个简单的函数,将数组的第一项和第二项调换,并返回两项之和:
function reverse(arr) {
return [arr[0], arr[1]]=[arr[1], arr[0]], arr[0] + arr[1]
}
const list = [1, 2]
reverse(list) // 返回 3,此时 list 为[2, 1]
逗号操作符对它的每个操作数求值(从左到右),并返回最后一个操作数的值。
expr1, expr2, expr3...
会返回最后一个表达式 expr3
的结果,其他的表达式只会进行求值。
3.零合并操作符 ??
零合并操作符 ??
是一个逻辑操作符,当左侧的操作数为 null
或者 undefined
时,返回右侧操作数,否则返回左侧操作数。
expr1 ?? expr2
空值合并操作符一般用来为常量提供默认值,保证常量不为 null 或者 undefined,以前一般使用 || 来做这件事 variable = variable || ‘bar’。然而,由于 || 是一个布尔逻辑运算符,左侧的操作数会被强制转换成布尔值用于求值。任何假值(0, ‘’, NaN, null, undefined)都不会被返回。这导致如果你使用 0、’’、NaN 作为有效值,就会出现不可预料的后果。
正因为 || 存在这样的问题,而 ?? 的出现就是解决了这些问题,?? 只会在左侧为 undefined
、null
时才返回后者,?? 可以理解为是 || 的完善解决方案。
可以在浏览器中执行下面的代码感受一下:
undefined || 'default' // 'default'
null || 'default' // 'default'
false || 'default' // 'default'
0 || 'default' // 'default'
undefined ?? 'default' // 'default'
null ?? 'default' // 'default'
false ?? 'default' // 'false'
0 ?? 'default' // 0
4. 可选链操作符 ?.
可选链操作符 ?. 允许读取位于连接对象链深处的属性的值,而不必验证链中的每个引用是否有效。?. 操作符的功能类似于 . 链式操作符,不同之处在于,在引用为 null
或者 undefined
的情况下不会引起错误,该表达式短路返回值是 undefined
。
当尝试访问可能不存在的对象属性时,可选链操作符将会使表达式更短、更简明。
const obj = {
a: 'foo',
b: {
c: 'bar'
}
}
console.log(obj.b?.c) // 输出 bar
console.log(obj.d?.c) // 输出 undefined
console.log(obj.func?.()) // 不报错,输出 undefined
以前可能会通过 obj && obj.a && obj.a.b
来获取一个深度嵌套的子属性,现在可以直接 obj?.a?.b
即可。
可选链除了可以用在获取对象的属性,还可以用在数组的索引 arr?.[index]
,也可以用在函数的判断 func?.(args)
,当尝试调用一个可能不存在的方法时也可以使用可选链。
调用一个对象上可能不存在的方法时(版本原因或者当前用户的设备不支持该功能的场景下),使用可选链可以使得表达式在函数不存在时返回 undefined
而不是直接抛异常。
const result = someInterface.customFunc?.()
5.私有方法/属性
在一个类里面可以给属性前面增加 #
私有标记的方式来标记为私有,除了属性可以被标记为私有外,getter/setter
也可以标记为私有,方法也可以标为私有。
class Person {
getDesc(){
return this.#name +' '+ this.#getAge()
}
#getAge(){
return this.#age } // 私有方法
get #name(){
return 'foo' } // 私有访问器
#age = 23 // 私有属性
}
const a = new Person()
console.log(a.age) // undefined 直接访问不到
console.log(a.getDesc()) // foo 23
6.位运算符 >> 与 >>>
有符号右移操作符 >> 将第一个操作数向右移动指定的位数,多余的位移到右边被丢弃,高位补其符号位,正数补 0,负数则补 1。因为新的最左位与前一个最左位的值相同,所以符号位(最左位)不会改变。
(0b111>>1).toString(2) // "11"
(-0b111>>1).toString(2) // "-100" 感觉跟直觉不一样
正数的好理解,负数怎么理解呢,负数在计算机中存储是按照补码来存储的,补码的计算方式是取反加一,移位时将补码形式右移,最左边补符号位,移完之后再次取反加一求补码获得处理后的原码。
-111 // 真值
1 0000111 // 原码(高位的0无所谓,后面加不到)
1 1111001 // 补码
1 1111100 // 算数右移
1 0000100 // 移位后求补码获得原码
-100 // 移位后的真值
一般我们用 >> 来将一个数除 2
,相当于先舍弃小数位然后进行一次 Math.floor
:
10 >> 1 // 5
13 >> 1 // 6 相当于
13.9 >> 1 // 6
-13 >> 1 // -7 相当于
-13.9 >> 1 // -7
无符号右移操作符 >>>
,将符号位作为二进制数据的一部分向右移动,高位始终补 0,对于正整数和算数右移没有区别,对于负数来说由于符号位被补 0,成为正数后就不用再求补码了,所以结果总是非负的。即便右移 0 个比特,结果也是非负的。
(0b111>>>1).toString(2) // "11"
(-0b111>>>1).toString(2) // "1111111111111111111111111111100"
7.位运算符 & 与 |
位运算符是按位进行运算,&
与、|
或、~
非、^
按位异或:
&: 1010 |: 1010 ~: 1010 ^: 1010
0110 0110 0110
---- ---- ---- ----
0010 1110 0101 1100
使用位运算符时会抛弃小数位,我们可以利用这个特性来给数字取整,比如给任意数字 & 上二进制的 32 个 1,或者 | 上 0,显而易见后者简单些。
所以我们可以对一个数字 | 0
来取整,负数也同样适用
1.3 | 0 // 1
-1.9 | 0 // -1
判断奇偶数除了常见的取余 % 2
之外,也可以使用 & 1
,来判断二进制数的最低位是不是 1,这样除了最低位之外都被置 0,取余的结果只剩最低位,是不是很巧妙。负数也同样适用:
const num = 3
!!(num & 1) // true
!!(num % 2) // true
8.双位运算符 ~~
可以使用双位操作符来替代正数的 Math.floor( )
,替代负数的 Math.ceil( )
。双否定位操作符的优势在于它执行相同的操作运行速度更快。
Math.floor(4.9) === 4 // true
// 简写为:
~~4.9 === 4 // true
不过要注意,对正数来说 ~~ 运算结果与 Math.floor( )
运算结果相同,而对于负数来说与 Math.ceil( )
的运算结果相同:
~~4.5 // 4
Math.floor(4.5) // 4
Math.ceil(4.5) // 5
~~-4.5 // -4
Math.floor(-4.5) // -5
Math.ceil(-4.5) // -4
PS:注意
~~(num/2)
方式和num >> 1
在值为负数时的差别
觉得写得不错的话,请用你们发财的小手点个赞叭!