- Published on
== 与 ===
===
):可预测的“全等”准则
严格相等 (===
被称为严格相等运算符或“全等”运算符。它的核心是拒绝任何类型转换,追求简单、可预测的结果,是代码健壮性的首选。
严格相等比较算法 (Strict Equality Comparison)
其算法可以概括为两个核心步骤:
- 类型检查:如果比较的两个值
x
和y
的类型不同,则直接返回false
。 - 值比较:如果类型相同,则进行同类型的值比较。
- 原始类型:比较值是否完全相等。 (特殊情况:
NaN === NaN
返回false
。这是NaN
的特性,它不等于任何值,包括它自身。) - 对象类型:比较引用 (内存地址) 是否指向同一个对象。两个独立创建、内容相同的对象,它们的引用不同,因此比较结果为
false
。
- 原始类型:比较值是否完全相等。 (特殊情况:
// 类型不同
'10' === 10; // false
// 类型相同,值不同
10 === 20; // false
// 类型相同,值相同 (引用类型)
const arr1 = [];
const arr2 = [];
arr1 === arr2; // false (它们在内存中的地址不同)
const refArr = arr1;
arr1 === refArr; // true (引用同一个对象)
==
):隐式转换的艺术
抽象相等 (==
被称为抽象或宽松相等运算符。它的复杂性源于其在比较不同类型的值时,会遵循一套精密的隐式类型转换规则。
抽象相等比较算法 (Abstract Equality Comparison)
下面是 ECMAScript 规范中定义的、x == y
的核心比较流程(按优先级顺序):
- 类型相同? 如果
x
和y
类型相同,则直接执行严格相等比较 (===
)。 null
与undefined
? 如果x
是null
而y
是undefined
(或反之),则返回true
。这是规范中的一条特殊规则。- 字符串与数字? 如果一个是字符串,一个是数字,则将字符串通过内部的
ToNumber()
操作转换为数字,然后再次进行比较。'10' == 10
→10 == 10
→true
- 布尔值参与比较? 如果其中一个是布尔值,则先将布尔值通过
ToNumber()
转换为数字(true
→1
,false
→0
),然后再次进行比较。true == '1'
→1 == '1'
→1 == 1
→true
- 对象与原始类型? 如果一个是对象,另一个是原始类型,则将对象通过内部的
ToPrimitive()
操作转换为原始值,然后再次进行比较。[10] == '10'
→'10' == '10'
→true
(数组[10]
通过ToPrimitive
变为字符串'10'
)
递归算法
这个算法是递归的。例如,在第4步中,布尔值转换为数字后,可能会产生一个新的“字符串与数字”的比较,此时会再次应用第3步的规则。
经典案例深度剖析
[] == ![]
为什么是 true
?
案例一:这个看似怪异的结果,是抽象相等算法多步骤应用的完美体现。
!
逻辑非运算符 (优先级最高)[]
是一个对象,在布尔上下文中为“真值 (truthy)”。- 因此
![]
首先被求值为false
。 - 表达式变为:
[] == false
布尔值参与比较 (规则 #4)
false
被ToNumber()
转换为0
。- 表达式变为:
[] == 0
对象与原始类型 (规则 #5)
- 左侧的
[]
对象需要通过ToPrimitive()
转换为原始值。 - 数组的
ToPrimitive
默认行为是调用其toString()
方法,得到一个空字符串''
。 - 表达式变为:
'' == 0
- 左侧的
字符串与数字 (规则 #3)
- 空字符串
''
被ToNumber()
转换为0
。 - 表达式变为:
0 == 0
- 空字符串
[] == []
为什么是 false
?
案例二:这遵循规则 #1。由于两边的类型相同(都是 object
),比较会退化为严格相等 (===
)。因为它们是两个在内存中独立创建的数组对象,其引用地址不同,所以返回 false
。