对于 JavaScript 中的一些运算符(如:==)来说是比较迷惑的。它们产生的结果有时会让我们变得混淆,那这一切的背后就是“隐式转换”在作祟了。
前置
Number 强制转换
target | number |
---|---|
undefined | NaN |
null | 0 |
true/false | 1/0 |
string | 解析失败返回 NaN |
BigInt | TypeError |
Symbol | TypeError |
Object | @@toPrimitive → valueOf() → toString() |
string 解析失败:
- 忽略前导与尾随的空格/行终止符
- 前导 0 不会识别为八进制文本
- +和-允许在字符串开头,且只能出现一次,后面不能跟空格
- Infinity 和 -Infinity 当作字面量
- 空串或全为空格解析成 0
- 不能含数字分割符
强制 boolean 转换
target | Boolean |
---|---|
0 / -0 | false |
null | false |
NaN | false |
undefined | false |
“” | false |
Object / “anything but not empty” | true |
复杂类型转换成原始类型
复杂数据类型转换为原始类型有以下规则:
- 强制原始值转换:@@toPrimitive → valueOf() → toString()
- 强制数字类型转换、强制 number 类型转换、强制 BigInt 类型转换:@@toPrimitive → valueOf() → toString()
- 强制字符串类型转换:@@toPrimitive → toString() → valueOf()
- Date 和 Symbol 对象是唯一重写 [@@toPrimitive] 方法的对象。Date.prototype[@@toPrimitive] 将 “default” hint 视为 “string”,而 Symbol.prototype[@@toPrimitive] 忽略 hint 并始终返回一个 symbol。
其中 [@@toPrimitive] 为一个内置的 Symbol 属性,其部署方式为:
1 | const obj = { |
操作符
==
首当其冲的肯定是 ”==“, 想必其已经臭名昭著了。那么我们先来看看它的一些坑坑吧 😀。
1 | {} == [] |
首先,我们明确一下 == 的规则,那么就非常容易去判断结果了,其规则为:
- 相同类型:
- Object 类型:比较引用地址
- Number: 有 NaN 则返回 false, +0 与 -0 相等,操作数相等则返回 true。
- String:只有当两个操作数具有相同的字符且顺序相同时才返回 true。
- Boolean:仅当操作数都为 true 或都为 false 时返回 true。
- BigInt:仅当两个操作数值相同时返回 true。
- Symbol:仅当两个操作数引用相同的符号时返回 true。
- null: 另一个数为 undefined 或 null 才返回 true。否则返回 false。
- undefined: 另一个数为 undefined 或 null 才返回 true。否则返回 false。
其中一个是对象,另一个是基本类型。会按照**前置**的规则进行转换成基本类型。
这时都是基本类型了,那么有如下规则。
- 都是基本类型,按照规则 1进行对比。
- 其中一个是 Symbol, 另一个不是,返回 false。
- 其中一个是 boolean, 另一个不是,则将 boolean 转换成数字。
- Number 与 String 对比:使用与 Number() 构造函数相同的算法将字符串转换为数字。转换失败将导致 NaN,这将保证相等是 false。
- Number 与 BigInt 对比:按数值进行比较。如果数值为 ±∞ 或 NaN,返回 false。
- String 与 BigInt 对比:使用与 BigInt() 构造函数相同的算法将字符串转换为 BigInt。如果转换失败,返回 false。
回到上题
1 | {} == [] |
+
在求值时,它首先将两个操作数强制转换为基本类型。然后,检查两个操作数的类型:
- 多个值运算,进行相加操作,如果其中有一个是字符串,那么会将另一个数转换成字符串,进行字符串连接。
- 多个值运算,都是 BigInt, 执行 BigInt 加法,一方是 BigInt,另一个不是,抛出 TypeError。
- 多个值运算,没有 String、BigInt, 都转为数字。
- 单值运算,转换成数字
下面来看一下常见的运算 😏
1 | {} + [] + {} + [] |
-
- 两个操作数:将两个操作数转换为数值,并根据两个操作数的类型执行数字减法或 BigInt 减法。如果类型不匹配,则抛出 TypeError。
- 一个操作数:数字则取反,否则转换成数字类型。
看看例题
1 | [] - {}; |
!
- 运算符将真值或假值转换为对应的相反值
- 当与非布尔值使用时,如果其操作数可以转化为 true,则返回 false,否则返回 true。
!!
双非运算符,将运算值转换成相应的 boolean 类型。
看看例题
1 | ![]; |
> / <
所有的比较操作符都是先强制转化左操作数再强制转化右操作数。
首先,通过依次调用其 [@@toPrimitive](以 “number” 作为提示)、valueOf() 和 toString() 方法,将对象转换为原始类型。左边的操作数总是在右边的操作数之前被强制转换。
- 如果两个值都是字符串,则根据它们所包含的 Unicode 码位的值,将它们作为字符串进行比较。
- 否则,尝试将非数值类型转化为数值类型。
- 如果一个为 NaN, 返回 false。
看看例题
1 | "a" < 3; |
总结
隐式转换的规则非常多,但都是向基本类型转换,所以理解复杂类型转换成原始类型比较重要,特别的还需要注意 {} 识别为一个 block 的情况。