各位观众老爷,大家好!今天咱们来聊聊 JavaScript 里让人头疼的“相等性”问题。说它头疼,是因为 JavaScript 提供了三种不同的相等性判断方式:==
(等于), ===
(严格等于), 和 Object.is()
。它们看似简单,实则暗藏玄机,一不小心就会掉进坑里。别怕,今天我就用最通俗易懂的语言,把它们扒个精光,让大家以后不再迷惑。
1. ==
(等于): 宽松的爱,灵活的匹配
首先,我们来聊聊 ==
。它就像一个比较随和的朋友,允许类型转换,只要转换后值相等,就认为它们相等。这种“宽松的爱”虽然灵活,但也容易产生意想不到的结果。
用代码说话:
console.log(1 == "1"); // true (字符串 "1" 被转换为数字 1)
console.log(0 == false); // true (false 被转换为数字 0)
console.log(null == undefined); // true (这是 JavaScript 的历史遗留问题)
console.log("0" == false); // true (false 被转换为数字 0, "0" 也被转换为数字 0)
console.log([] == false); // true ([] 先转换为字符串 '', 然后转换为数字 0,false 转换为数字 0)
console.log([0] == false); // true ([0] 先转换为字符串 '0', 然后转换为数字 0,false 转换为数字 0)
console.log([1] == true); // true ([1] 先转换为字符串 '1', 然后转换为数字 1,true 转换为数字 1)
console.log(new String("foo") == "foo"); // true (String 对象被转换为原始字符串 "foo")
从上面的例子可以看出,==
在比较之前会尝试进行类型转换。具体规则如下:
- 如果类型相同: 那么直接比较值。
- 如果类型不同:
null
和undefined
相等。- 数字和字符串比较,字符串会先被转换为数字。
- 布尔值会先被转换为数字 (true -> 1, false -> 0)。
- 对象和字符串/数字比较,对象会先调用
valueOf()
方法,如果返回原始值,则按照原始值进行比较;否则,调用toString()
方法,如果返回原始值,则按照原始值进行比较。 如果两个方法都没有返回原始值,或者返回的是对象,那么结果就是false
。
需要注意的是: ==
不会转换 NaN。 NaN == NaN
的结果永远是 false
。
2. ===
(严格等于): 一丝不苟的完美主义者
与 ==
的宽松不同,===
是一位一丝不苟的完美主义者。它要求类型和值都必须完全相等,才会返回 true
。不允许任何类型转换。
console.log(1 === "1"); // false (类型不同)
console.log(0 === false); // false (类型不同)
console.log(null === undefined); // false (类型不同)
console.log("0" === false); // false (类型不同)
console.log([] === false); // false (类型不同)
console.log(new String("foo") === "foo"); // false (类型不同, 一个是对象,一个是字符串)
let a = [1,2,3];
let b = a;
console.log(a === b); //true (引用同一个对象)
let c = [1,2,3];
console.log(a === c); //false (引用不同的对象,即使内容相同)
总结一下:
- 类型不同: 肯定返回
false
。 - 类型相同:
- 数字: 值相同则返回
true
,否则返回false
。 特殊情况:+0 === -0
返回true
,NaN === NaN
返回false
。 - 字符串: 值相同(包含大小写)则返回
true
,否则返回false
。 - 布尔值: 值相同则返回
true
,否则返回false
。 null
和undefined
:null === null
返回true
,undefined === undefined
返回true
。- 对象: 只有当它们引用的是同一个对象时,才会返回
true
。即使两个对象拥有相同的属性和值,如果它们是不同的对象,也会返回false
。
- 数字: 值相同则返回
3. Object.is()
: 特立独行的思考者
Object.is()
是 ES6 新增的方法,它在 ===
的基础上做了一些细微的调整,主要体现在对 NaN
和 +0
和 -0
的处理上。
console.log(Object.is(1, "1")); // false (类型不同)
console.log(Object.is(0, false)); // false (类型不同)
console.log(Object.is(null, undefined)); // false (类型不同)
console.log(Object.is(NaN, NaN)); // true (Object.is 认为 NaN 等于 NaN)
console.log(NaN === NaN); // false (=== 认为 NaN 不等于 NaN)
console.log(Object.is(+0, -0)); // false (Object.is 认为 +0 和 -0 不相等)
console.log(+0 === -0); // true (=== 认为 +0 和 -0 相等)
let a = [1,2,3];
let b = a;
console.log(Object.is(a, b)); //true (引用同一个对象)
let c = [1,2,3];
console.log(Object.is(a, c)); //false (引用不同的对象,即使内容相同)
总结一下:
Object.is()
在大多数情况下和===
的行为一致。Object.is(NaN, NaN)
返回true
,而NaN === NaN
返回false
。Object.is(+0, -0)
返回false
,而+0 === -0
返回true
。
4. 为什么要区分这三种相等性判断方式?
- 历史原因: JavaScript 的早期设计中,
==
的类型转换规则比较复杂,容易导致一些意想不到的错误。 - 代码可读性和可维护性: 使用
===
可以更清晰地表达你的意图,避免类型转换带来的潜在问题,提高代码的可读性和可维护性。 - 特殊场景: 在某些特殊场景下,例如需要区分
+0
和-0
,或者需要判断NaN
是否等于自身时,Object.is()
就派上了用场。
5. 最佳实践:该用哪个?
总的来说,强烈建议使用 ===
(严格相等) 来进行相等性判断。 除非你明确知道自己在做什么,并且需要利用 ==
的类型转换特性。
- 优先使用
===
: 避免类型转换带来的潜在问题,提高代码可读性。 - 谨慎使用
==
: 只有在明确需要类型转换时才使用,并且要充分了解其类型转换规则。 - 特殊情况下使用
Object.is()
: 例如需要区分NaN
或+0
和-0
时。
6. 案例分析:真假美猴王
假设我们有两只猴子,一只真的,一只假的(克隆猴)。它们长得一模一样,属性也完全相同。
const 真的猴子 = {
name: "孙悟空",
age: 500,
weapon: "金箍棒"
};
const 假的猴子 = {
name: "孙悟空",
age: 500,
weapon: "金箍棒"
};
console.log(真的猴子 == 假的猴子); // false (引用不同的对象)
console.log(真的猴子 === 假的猴子); // false (引用不同的对象)
console.log(Object.is(真的猴子, 假的猴子)); // false (引用不同的对象)
const 另一只真的猴子 = 真的猴子; //引用同一个对象
console.log(真的猴子 == 另一只真的猴子); // true (引用同一个对象)
console.log(真的猴子 === 另一只真的猴子); // true (引用同一个对象)
console.log(Object.is(真的猴子, 另一只真的猴子)); // true (引用同一个对象)
在这个例子中,即使两只猴子的属性完全相同,但它们仍然是不同的对象,因此使用 ==
、===
和 Object.is()
都会返回 false
。只有当它们引用的是同一个对象时,才会返回 true
。
7. 表格总结:一目了然
为了方便大家记忆,我把这三种相等性判断方式的特点总结成一个表格:
特性 | == (等于) |
=== (严格等于) |
Object.is() |
---|---|---|---|
类型转换 | 允许 | 不允许 | 不允许 |
NaN 的比较 | NaN == NaN 返回 false |
NaN === NaN 返回 false |
Object.is(NaN, NaN) 返回 true |
+0 和 -0 的比较 | +0 == -0 返回 true |
+0 === -0 返回 true |
Object.is(+0, -0) 返回 false |
对象的比较 | 比较引用 (Reference) | 比较引用 (Reference) | 比较引用 (Reference) |
使用场景 | 谨慎使用,除非明确需要类型转换 | 推荐使用,避免潜在问题 | 特殊场景,区分 NaN 和 +0/-0 |
可读性 | 较低 | 较高 | 较高 |
8. 一些补充说明
- 在比较对象时,
==
、===
和Object.is()
都是比较的是引用,而不是对象的内容。如果需要比较对象的内容,需要自己实现比较逻辑,例如可以使用JSON.stringify()
将对象转换为字符串,然后进行比较,但这并不是一个完美的解决方案,因为它对属性的顺序敏感。更好的方法是递归地比较对象的每一个属性。 Object.is()
的一个应用场景是判断变量是否为NaN
,因为NaN === NaN
永远返回false
。你可以使用Object.is(value, NaN)
来判断一个变量是否为NaN
。- 在使用
==
进行比较的时候,需要特别注意一些特殊情况,例如比较null
和undefined
,比较字符串和数字,比较对象和原始值等。 了解这些特殊情况可以避免一些意想不到的错误。
9. 练习题
为了检验大家是否掌握了今天的内容,我给大家留几道练习题:
"1" == 1
的结果是什么?"1" === 1
的结果是什么?Object.is("1", 1)
的结果是什么?NaN == NaN
的结果是什么?NaN === NaN
的结果是什么?Object.is(NaN, NaN)
的结果是什么?+0 == -0
的结果是什么?+0 === -0
的结果是什么?Object.is(+0, -0)
的结果是什么?[] == ![]
的结果是什么? (这题有点难度,考验你对类型转换的理解)
答案:
true
false
false
false
false
true
true
true
false
true
(![]
的结果是false
,[] == false
的结果是true
)
好了,今天的讲座就到这里。希望大家通过今天的学习,能够彻底理解 JavaScript 的相等性判断,以后在写代码的时候不再迷惑,写出更健壮、更可维护的代码。 感谢大家的观看!