=== 和 == 的规则及原理
文章目录
- 一、前言
- 二、设计的原因(个人理解)
- 三、== 转换规则
- 3.1 == 相同类型的比较
- 3.2 == 不同类型的比较
- 四、== 和 === 的代码简单实现
- 4.1 === 实现
- 4.2 == 实现
一、前言
前段时间在面试时,被问到 JavaScript 中 ==
和 ===
的区别。
当时就简单的回答了一下八股,== 会进行类型转换再比较,=== 不会进行转换,必须值和类型都相同。这个答案没什么问题,但是感觉理解有些太浅了,所以我就在想:
- JS为什么要设计这两个功能比较重复的比较运算符呢?
==
的隐式类型转换到底发生了什么?- 底层是什么机制导致的这种差别呢?
今天我就围绕着三个问题说一下自己查到的和理解。
二、设计的原因(个人理解)
先看JS的历史背景,在1995年由 Brendan Eich 在 10 天里快速设计出来的。当时的目标可能就是为了能够快速在浏览器中快速实现页面的交互,所以当时的设计目标不具备很强的严谨性。所以 == 的实现会自动“兜底”,就是会进行隐式类型转换。
在ES3之后为了弥补 == 的问题,在规范中添加了 === (严格相等), 不做隐式转换,要求类型和值完全一致。
为什么不直接废掉 == ?
JS 已经在浏览器里广泛运行,改动会破坏成千上万的网站。而且在某些场景中 == 也有好处,比如:
if(x == null) {// 等价于 x === null || x === undefined
}
这是一种社区里仍然存在的“惯用法”。
三、== 转换规则
===(严格相等比较) 逻辑很简单:
- 类型不同 -> 直接 false
- 类型相同 -> 比较值(对象比较引用)
==(抽象相等比较) 逻辑由于有隐式类型转换,相对麻烦一些,根据MDN中的描述,简单来梳理一下 == 的转换规则:
3.1 == 相同类型的比较
如果两个操作数类型相同,比较规则如下:
对象: 只有当他们引用同一个对象时,结果才是 true。
{} == {} // false, 不同引用
let a = {};
let b = a;
a == b; // true, 同一引用
字符串: 必须字符和顺序完全相同。
'abc' == 'abc' // true
'abc' == 'cba' // false
数字: 数值相同则返回 true。
- 特殊情况:+0 == -0 => true
- NaN 永远不等于任何值(包括自己)
0 == -0 // true
NaN == NaN // falses
布尔: 只有在都为true或都为false时返回true。
true == true // true
false == true // false
大整数(BigInt): 值相同即为 true
1n == 1n // true
符号(Symbol): 不是同一个符号引用
Symbol() == Symbol() // false
const s1 = Symbol()
const s2 = s1;
s2 == s1 // true
3.2 == 不同类型的比较
当类型不同,会进行以下规则:
null 和 undefined
- 只有 null == undefined 为 true。
- 与其他任何值比较都为 false。
null == undefined // true
null == 0 // false
布尔 vs 其他
- 布尔会先转为数字(true → 1,false → 0)。
true == 1 // true
false == 0 // true
字符串 vs 数字
- 字符串会转为数字再比较。
'42' == 42 // true
'abc' == NaN // false
对象 vs 原始值
- 对象会先用 ToPrimitive 转换为原始值(通过 Symbol.toPrimitive、valueOf、toString 等)。
[] == '' // true ([] → '')
[] == 0 // true ([] → '' → 0)
[1] == 1 // true ([1] → '1' → 1)
这部分涉及 ToPrimitive (把对象转换原始值)这个抽象操作,请看另一篇文章。
数字 vs BigInt
- 按数值比较。
- 如果数字是 ±Infinity 或 NaN,则结果为 false。
1n == 1 // true
1n == NaN // false
字符串 vs BigInt
-
尝试把字符串转为 BigInt 再比较。
-
转换失败则返回 false。
'10' == 10n // true
'abc' == 10n // false
符号 vs 非符号
- 永远返回 false。
Symbol() == 'symbol' // false
四、== 和 === 的代码简单实现
4.1 === 实现
function strictEqual(x, y) {// 类型不同直接 falseif (typeof x !== typeof y) return false;return x === y; // 值比较
}
4.2 == 实现
在 ECMAScript® Language Specification §7.2.14 里,== 的比较规则大概是这样:
- 类型相同 → 按 === 比较。
- null 和 undefined → 相等。
- number 和 string → string → number,再比较。
- boolean → 转换成 number,再比较。
- object 和原始值 → object 转换成原始值(调用 ToPrimitive),再比较。
- 其他情况 → 不相等。
// ToPrimitive 转换(简化实现)
// 实际规范有 PreferredType 参数,这里我们用默认的
function toPrimitive(input) {if (typeof input !== "object" || input === null) return input;// 尝试 valueOfif (typeof input.valueOf === "function") {const val = input.valueOf();if (typeof val !== "object") return val;}// 尝试 toStringif (typeof input.toString === "function") {const val = input.toString();if (typeof val !== "object") return val;}throw new TypeError("Cannot convert object to primitive");
}function abstractEqualityComparison(x, y) {// 1. 类型相同 → 用 ===if (typeof x === typeof y) {return x === y;}// 2. null 和 undefinedif (x == null && y == null) {return true;}// 3. number 和 stringif (typeof x === "number" && typeof y === "string") {return abstractEqualityComparison(x, Number(y));}if (typeof x === "string" && typeof y === "number") {return abstractEqualityComparison(Number(x), y);}// 4. boolean → 转 numberif (typeof x === "boolean") {return abstractEqualityComparison(Number(x), y);}if (typeof y === "boolean") {return abstractEqualityComparison(x, Number(y));}// 5. object 和 原始值if ((typeof x === "object" && x !== null) && (typeof y !== "object" || y === null)) {return abstractEqualityComparison(toPrimitive(x), y);}if ((typeof y === "object" && y !== null) && (typeof x !== "object" || x === null)) {return abstractEqualityComparison(x, toPrimitive(y));}// 6. 其他情况return false;
}console.log(abstractEqualityComparison(1, '1')); // true
console.log(abstractEqualityComparison(true, 1)); // true
console.log(abstractEqualityComparison(null, undefined)); // true
console.log(abstractEqualityComparison([], 0)); // true
console.log(abstractEqualityComparison([1,2], "1,2")); // true
console.log(abstractEqualityComparison({}, "[object Object]")); // true
console.log(abstractEqualityComparison(0, false)); // true
console.log(abstractEqualityComparison(NaN, NaN)); // false