JS `Equality` (相等性) 判断:`==`, `===`, `Object.is` 的细微差别

各位观众老爷,大家好!今天咱们来聊聊 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")

从上面的例子可以看出,== 在比较之前会尝试进行类型转换。具体规则如下:

  • 如果类型相同: 那么直接比较值。
  • 如果类型不同:
    • nullundefined 相等。
    • 数字和字符串比较,字符串会先被转换为数字。
    • 布尔值会先被转换为数字 (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 返回 trueNaN === NaN 返回 false
    • 字符串: 值相同(包含大小写)则返回 true,否则返回 false
    • 布尔值: 值相同则返回 true,否则返回 false
    • nullundefined null === null 返回 trueundefined === 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
  • 在使用 == 进行比较的时候,需要特别注意一些特殊情况,例如比较 nullundefined,比较字符串和数字,比较对象和原始值等。 了解这些特殊情况可以避免一些意想不到的错误。

9. 练习题

为了检验大家是否掌握了今天的内容,我给大家留几道练习题:

  1. "1" == 1 的结果是什么?
  2. "1" === 1 的结果是什么?
  3. Object.is("1", 1) 的结果是什么?
  4. NaN == NaN 的结果是什么?
  5. NaN === NaN 的结果是什么?
  6. Object.is(NaN, NaN) 的结果是什么?
  7. +0 == -0 的结果是什么?
  8. +0 === -0 的结果是什么?
  9. Object.is(+0, -0) 的结果是什么?
  10. [] == ![] 的结果是什么? (这题有点难度,考验你对类型转换的理解)

答案:

  1. true
  2. false
  3. false
  4. false
  5. false
  6. true
  7. true
  8. true
  9. false
  10. true![] 的结果是 false[] == false 的结果是 true

好了,今天的讲座就到这里。希望大家通过今天的学习,能够彻底理解 JavaScript 的相等性判断,以后在写代码的时候不再迷惑,写出更健壮、更可维护的代码。 感谢大家的观看!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注