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