Appearance
11. 巧用 JS 中的运算符
1. 其他类型转数字类型
- 使用单目运算符
+
,相当于Number(target)
js
+target
1
对于 target
,分三种情况:
- 当 target 为数字型字符串(大多数情况),则直接转为数字类型
- 当 target 为对象类型,底层调用
valueOf
或toString
方法,假如调用后返回结果为value
,然后再使用Number(value)
进行转换 - bigint 类型无法转换为 number 类型
例 1.1
js
let a = '3.14', b = '10'
(+a) + (+b) // 括号可省,空格不可省
// 13.14
1
2
3
2
3
例 1.2
js
let arr = [1, 2, 3]
arr.valueOf = () => arr.reduce((a, b)=> a + b, 0)
+arr // 6
Number(arr) // 6
1
2
3
4
5
2
3
4
5
2. 其他类型转布尔类型
使用 !!
可强制转换为对应的布尔类型,相当于 Boolean()
。
js
!!target
1
根据 JS 中类型转换为布尔类型时是转为 true
还是 false
,将 JS 中的类型分为两种:
falsy
:布尔转换后的值为false
的类型truthy
:布尔转换后的值为true
的类型
JS 中所有的 falsy
类型一共有 8 个:false
、0
、-0
、0n
、''
、NaN
、null
、undefined
除此之外,其他都是 truthy
类型。
3. 浮点数取整
我们一般使用以下方法进行取整:
Math.floor()
:向下取整Math.ceil()
:向上取整Math.round()
:四舍五入取整parseInt()
:注意参数问题;当数字过大或过小时,有坑,具体见【奇技淫巧】中第 5 节
使用位运算有更简洁的方法:
x | 0
js
1.14 | 0 // 1
-5.14 | 0 // -5
0.19 | 0 // 0
1
2
3
2
3
~~x
js
~~ 10.5 // 10
~~ -5.8 // -5
~~ 0.15 // 0
1
2
3
2
3
x ^ 0
js
3.14 ^ 0 // 3
-3.14 ^ 0 // -3
0.14 ^ 0 // 0
1
2
3
2
3
x & -1
js
3.14 & -1 // 3
-3.14 & -1 // -3
0.14 & -1 // 0
1
2
3
2
3
原理:计算机中位运算会截断浮点数的小数部分,所以只要找到一个位运算表达式,使得其值仍为原值。
不熟悉位运算的移步:MDN 位运算
注意
由于 JS 中位运算只支持 32 位有符号整数(实际上是 在进行位运算之前,会被强制转换成 32 位有符号整数,多出的位数会被截断),因此当要取整的数字不在 [-2^31, 2^31)
范围内,不要使用此类方法。
JS 的数组索引是 32 位置无符号整数,范围为 [0, 2^32)
,虽然比位运算的范围大,但基本上不会遇到这么大的索引,因此在数组索引中可以放心使用。
4. 取半/对折数字
将一个数字进行对半处理(n/2
),一般情况下我们都是要整除的结果,例如在二分查找,或者取数组的中间索引的时候。一般都能写出下面的代码:
js
let midIndex = Math.floor((low + high) / 2)
1
但这样看起来复杂很多,使用 x >> 1
,可一步代替上述代码:
js
let midIndex = (low + high) >> 1
1
例 4.1
js
7 >> 1 // 3
10 >> 1 // 5
1
2
2
5. 判断奇偶
一般情况下,我们使用取余 %
判断:
js
if (n % 2 === 1) {
// n 为奇数
}
1
2
3
2
3
使用位运算更简洁(理论上计算速度也比取余快一点):
js
if (n & 1) {
// n 为奇数
}
1
2
3
2
3
6. 变量互换
- 常规方法:
js
let temp = x
x = y
y = temp
1
2
3
2
3
- 解构赋值:
js
[x, y] = [y, x]
1
- 位运算实现(仅适用于 32 位有符号整数范围内的数字)
js
let n1 = 1, n2 = 2
n1 ^= n2
n2 ^= n1
n1 ^= n2
1
2
3
4
2
3
4
7. 2 的 n 次方
常规方法是使用 2 ** n
或 Math.pow(2, n)
,也可以使用位运算实现:
js
2 << (n - 1) // 2 的 n 次方,n >= 1
1
8. 短路求值
短路求值(Short-circuit evaluation)在是指一个表达式在计算过程中遇到了满足条件的值(falsy
或 truthy
)之后,停止继续向后计算,返回该值。就像电路短路了一样。
JS 中使用逻辑运算符 &&
和 ||
进行短路求值。
- && 短路求值:从左到右计算,对每一项进行运算,直到遇到了第一个
falsy
的值,停止向右执行,并返回该值。
js
exp1 && exp2 && ...
1
例如,下面代码中 res
为 NaN
,且 i
为 0
js
let i = 0
let res = 'foo' && 'bar' && NaN && i++
1
2
2
- || 短路求值:从左到右计算,对每一项进行运算,直到遇到了第一个
truthy
的值,停止向右执行,并返回该值。
js
exp1 || exp2 || ...
1
例如,下面代码中 res
为 0
,且 j
为 1
js
let j = 0
let res = '' || 0 || j++
1
2
2
9. 空值合并运算符
ES2020(ES11)新增逻辑运算符 ??
(空值合并运算符)来判断未定义行为的变量。具体来说:
js
a ?? b
1
对于上述表达式,若 a
为 undefined/null
,则返回 b
;否则返回 a
。
实际上,??
是一个语法糖,相当于下面这个三元表达式:
js
(a !== undefied && a !== null) ? a : b
1
??
和 ||
的区别:
??
返回第一个定义了的(非null/undefined)的值||
返回第一个truthy
类型的值
注意
??
不能直接和 &&
、||
一起连用,除非使用了括号标明优先级。
例 9.1
js
// 报错
1 && null ?? 'foo'
// 合法
(1 && null) ?? 'foo' // 'foo'
1
2
3
4
2
3
4
例 9.2 ??=
js
// 给一个变量赋值,若这个变量非 undefine/null 则保持原值
let foo = null
foo ??= 'foo' // 'foo'
1
2
3
2
3
10. 可选链
可选链 ?.
是一种访问嵌套对象属性的安全的方式。即使中间的属性不存在,也不会出现错误,而是返回 undefined
。
三种形式:obj?.prop
、obj?.['prop']
、obj.method?.()
- 访问对象属性
js
// 若用户未设置性别,默认为 male
let sex = user?.info?.sex ?? 'male'
1
2
2
上述代码等价于:
js
let sex = user?.['info']?.['sex'] ?? 'male'
1
- 执行函数
js
let obj = {}
obj.foo() // 报错
obj.foo?.() // 不报错
1
2
3
2
3
实际上,a?.b?.c
等价于 a.b && a.b.c
,更简洁。
Vue3 源码里,尤雨溪没用使用可选链 ?.
,而是使用了 &&
。原因是 ?.
编译后的代码比 &&
多出不少。考虑到要尽量减少依赖包的大小,没用使用可选链。尤大是从开源角度考虑的,语法糖本就是为了方便开发者,所以是否使用看你自己了。
11. 逗号运算符
逗号操作符,对它的每个操作数求值(从左到右),并返回最后一个操作数的值。
例 11.1
js
let i = 1, j = 1
let res = (i + j, i++, j++)
// res:1, i:2, j:2
1
2
3
2
3
例 11.2 交换数组中指定项的值,并返回两数之和
js
function exchange(arr, i, j) {
return (
[arr[i], arr[j]] = [arr[j], arr[i]], // 交换两值
arr[i] + arr[j] // 返回两数之和
)
}
1
2
3
4
5
6
2
3
4
5
6
12. 数值分割符号
严格上不算操作符了。
ES2021 引入下划线 _
可以对数字(整数和浮点数)进行分割,提高可读性,在十进制、二进制、十六等各种进制以及 BigInt
中都可以使用。
js
10_000_000 // 等价于 10000000
0b1100_1010 // 等价于 0b11001010
0xFFFF_FFFF // 等价于 0xFFFFFFFF
9_999_999_999n // 等价于 9999999999n
1
2
3
4
2
3
4