JS 中的相等运算
JavaScript 提供三种不同的值比较操作:严格相等、抽象 (非严格) 相等还有 Object.is()。
抽象相等
抽象相等 (Abstract Equality),也称宽松相等 (Loose Equality),操作符为 == 和 !=。特点是会对类型不同的两个操作数进行强制类型转换。
类型相同
| 类型 | 比较结果 |
|---|---|
布尔值 boolean | 都为 true 或者 false 时返回 true |
字符串 string | 为相同字符时返回 true |
数字 number | 值相同时返回 true, +0 和 -0 认为值相同,其中一个是 NaN 时返回 false (NaN 不等于任何值) |
大整形 bigint | 值相同时返回 true, 0n 和 -0n 认为值相同。 |
符号 symbol | 引用相同时返回 true。 |
对象 object | 引用相同时返回 true。 |
null 与 undefined
当其中一个操作数是 null/undefined 时,只有另一个也是 null/undefined 时返回 true。
document.all 被视为 undefined,所以 document.all == undefined,document.all == null,但是 document.all == null && document.all == undefined 为 false。
类型不同
基本类型之间
| 类型 | 比较结果 |
|---|---|
Boolean 和其他值 | 将布尔值转换为数字。true -> 1,false -> 0 |
Number 和 String | 使用 Number()转换后比较,转换失败则为 NaN,再进行比较 |
Number 和 BigInt | 比较数值,如果数值为 ±∞ 或 NaN,返回 false |
BigInt 和 String | 使用 BigInt() 转换后比较,转换失败则返回 false |
Symbol与其他类型比较始终返回false
基本类型和引用类型之间
按照以下顺序,先把引用类型转换为基本类型:
- 调用该对象的
@@toPrimitive(hint)方法 (以default为hint); - 调用该对象的
valueOf()方法; - 调用该对象的
toString()方法。
转换之后就都变成了基本类型,如果类型相同则按照上述 类型相同 规则进行比较; 如果类型不同则按照上述 基本类型之间 规则进行比较。
关于 @@toPrimitive
toPrimitive() 方法是 JavaScript 的内部实现,该函数接受一个 hint 作为参数,hint 只能是 default number 或 string。可以通过以下代码查看、调用该方法:
目前 JS 内置对象只有 Date 对象实现了该方法。
Date.prototype[Symbol.toPrimitive];
// ƒ [Symbol.toPrimitive]() { [native code] }
const date = new Date(1686131708577);
date[Symbol.toPrimitive]('default');
// 'Wed Jun 07 2023 17:55:08 GMT+0800 (中国标准时间)'
date[Symbol.toPrimitive]('string');
// 'Wed Jun 07 2023 17:55:08 GMT+0800 (中国标准时间)'
date[Symbol.toPrimitive]('number');
// 1686131708577
所以在 Date 类型和原始值进行比较的时候:
new Date(1686131708577) == 'Wed Jun 07 2023 17:55:08 GMT+0800 (中国标准时间)'; // true
常见对象的转换为基本类型
| 对象类型 | 转换方式 | 转换结果 | 示例 |
|---|---|---|---|
数组 Array | Array.prototype.toString | 逗号 , 拼接的字符串 | [1,2,3] -> '1,2,3' |
数组 Array | Array.prototype.toString | 逗号 , 拼接的字符串 | [1,2,3] -> '1,2,3' |
对象 Object | Object.prototype.toString | '[object Object]' | |
正则 RegExp | RegExp.prototype.toString | 正则内容对应的字符串 | |
函数 Function | Function.prototype.toString | 函数内容体字符串 |
严格相等
严格相等 (Strict Equality),操作符为 === 和 !==,特点是不同类型不会进行类型转换。
类型相同
| 类型 | 比较结果 |
|---|---|
布尔值 boolean | 都为 true 或者 false 时返回 true |
字符串 string | 为相同字符时返回 true |
数字 number | 值相同时返回 true, +0 和 -0 认为值相同,其中一个是 NaN 时返回 false (NaN 不等于任何值) |
大整形 bigint | 值相同时返回 true, 0n 和 -0n 认为值相同。 |
符号 symbol | 引用相同时返回 true。 |
null | 只与 null 相等。 |
undefined | 只与 undefined 相等。 |
对象 object | 引用相同时返回 true。 |
类型不同
直接返回 false
关于 Object.is()
Object.is() 是 ES6 新增的第三种判断相等方式,Object.is 的行为方式与严格相等基本相同,但是对于 NaN 和 -0 和 +0 进行特殊处理:
NaN === NaN; // false
Object.is(NaN, NaN); // true
+0 === -0; // true
Object.is(+0, -0); //false
陷阱误区
抽象相等和布尔转换不同
转换成布尔值
手动转换函数:Boolean()
| 值 | 转换成布尔值的结果 |
|---|---|
| null | false |
| undefined | false |
| 布尔值 | 不需要转换 |
| 数字 | 0, NaN 转为 false,其他为 true |
| 字符串 | ''(空串)转为 false,其他为 true |
| 对象 | 总是为 true |
但是请注意,这只是其它类型的值转换为布尔值的结果,并不意味着它们与布尔值之间的等价关系。看看下面的例子:
//数字
2 == true; // false; 理解为 2 === 1
2 == false; // false; 2 === 0
1 == true; // true; 1 === 1
0 == false; // true;0 === 0
//字符串,不是所有的非空字符串都==true
'' == false; // true; 0 === 0
'1' == true; // true; 1 === 1
'2' == true; // false; 2===1
'abc' == true; // false; NaN===1
抽象相等中的字符串
字符串转成数字 手动转换函数:Number()
| 字符串 | 转换为数字的结果 |
|---|---|
| undefined | NaN |
| null | 0 |
| 布尔值 | false 转换为 0,true 转换为 1 |
| 数字 | 不需要转换 |
| 字符串 | 解析字符串中的数字(忽略开头和结尾的空格)若无法转换则为 NaN;空串转为 0;如Number('123')->123;Number('123a')->NaN |
| 对象 | 调用 ToPrimitive(value, number) 转换成原始类型 |
//有一些可能是我们预期的结果
'abc' == new String('abc'); // true; 'abc'==='abc'
'123' == 123; // true; 123===123
'' == 0; // true; 0 === 0
//但有时候也存在一些问题
'\n\t123\r' == 123; // true
抽象相等中的对象
如果比较对象和非对象,他们会被转换成原始值,导致产生一些奇怪的结果
({}) == "[object Object]" // true
'[123]' == 123 // true
[] == 0 // true