Skip to content

JavaScript 等性比较:==、=== 与 Object.is()

在日常生活中,判断两个东西是否相同有不同的标准。比如两个双胞胎,从外表看"相同",但从 DNA 看是"相似"。JavaScript 的等性比较也有类似的概念:有时我们只关心值是否相同(==),有时我们要求值和类型都相同(===),还有时需要更严格的判断(Object.is())。

三种比较方式概览

JavaScript 提供了三种等性比较方式:

javascript
// 1. 相等运算符 ==(宽松相等)
console.log(5 == "5"); // true(允许类型转换)

// 2. 全等运算符 ===(严格相等)
console.log(5 === "5"); // false(不允许类型转换)

// 3. Object.is()(最严格)
console.log(Object.is(55)); // true
console.log(Object.is(NaNNaN)); // true(与 === 不同)

相等运算符 ==(抽象相等)

相等运算符 == 在比较时会进行类型转换,试图让两边的类型一致后再比较。

基本规则

javascript
// 相同类型直接比较
console.log(5 == 5); // true
console.log("hello" == "hello"); // true
console.log(true == true); // true

// 不同类型会转换
console.log(5 == "5"); // true(字符串转为数字)
console.log(0 == false); // true(布尔值转为数字)
console.log(1 == true); // true
console.log("" == false); // true(都转为 0)

// null 和 undefined 特殊规则
console.log(null == undefined); // true(特殊规定)
console.log(null == 0); // false
console.log(undefined == 0); // false

类型转换规则

当使用 == 比较不同类型的值时,JavaScript 会按照以下规则进行转换:

javascript
// 规则 1:字符串与数字比较,字符串转数字
console.log("42" == 42); // true("42" 转为 42)
console.log("0" == 0); // true
console.log("" == 0); // true(空字符串转为 0)

// 规则 2:布尔值转为数字(true → 1, false → 0)
console.log(true == 1); // true
console.log(false == 0); // true
console.log(true == "1"); // true(true → 1,"1" → 1)
console.log(false == ""); // true(false → 0,"" → 0)

// 规则 3:对象与原始值比较,对象转为原始值
let obj = { valueOf: () => 42 };
console.log(obj == 42); // true(调用 valueOf())

let arr = [12];
console.log(arr == "1,2"); // true(调用 toString())

// 规则 4:null 和 undefined 只相等于自己和对方
console.log(null == undefined); // true
console.log(null == null); // true
console.log(undefined == undefined); // true
console.log(null == 0); // false
console.log(null == ""); // false
console.log(undefined == 0); // false

== 的陷阱

javascript
// 陷阱 1:空字符串与 0
console.log("" == 0); // true
console.log("0" == 0); // true
console.log("" == "0"); // false(都是字符串,不转换)

// 陷阱 2:空数组
console.log([] == 0); // true([] → "" → 0)
console.log([] == ""); // true([] → "")
console.log([] == false); // true

// 陷阱 3:对象比较
console.log({} == {}); // false(不同对象,不同引用)
console.log([] == []); // false(不同数组,不同引用)

// 陷阱 4:奇怪的结果
console.log([] == ![]); // true
// 解析:![] → false, [] == false → true

console.log("" == []); // true
console.log("" == [null]); // true
console.log(0 == []); // true

// 陷阱 5:true 不等于 "true"
console.log(true == "true"); // false
// true → 1,"true" → NaN,1 != NaN

== 的实际应用

虽然 == 有很多陷阱,但在某些场景下很方便:

javascript
// 检查 null 或 undefined
function processValue(value) {
  if (value == null) {
    // 同时匹配 null 和 undefined
    return "No value";
  }
  return value;
}

console.log(processValue(null)); // "No value"
console.log(processValue(undefined)); // "No value"
console.log(processValue(0)); // 0
console.log(processValue("")); // ""

// 等价于更繁琐的写法:
if (value === null || value === undefined) {
  // ...
}

全等运算符 ===(严格相等)

全等运算符 === 不进行类型转换,要求类型和值都相同才返回 true。

基本规则

javascript
// 相同类型和值
console.log(5 === 5); // true
console.log("hello" === "hello"); // true
console.log(true === true); // true

// 不同类型直接返回 false
console.log(5 === "5"); // false
console.log(0 === false); // false
console.log(1 === true); // false
console.log("" === 0); // false

// null 和 undefined 不相等
console.log(null === undefined); // false
console.log(null === null); // true
console.log(undefined === undefined); // true

=== 的优势

javascript
// 行为可预测
console.log(0 === false); // false(清晰明确)
console.log("" === 0); // false
console.log([] === 0); // false

// 避免类型转换的坑
let userInput = "0";

if (userInput == false) {
  // true(可能不是你想要的)
  console.log("This runs!");
}

if (userInput === false) {
  // false(符合预期)
  console.log("This won't run");
}

// 更安全的比较
function isValidNumber(value) {
  return typeof value === "number" && !isNaN(value);
}

console.log(isValidNumber(42)); // true
console.log(isValidNumber("42")); // false
console.log(isValidNumber(NaN)); // false

=== 的特殊情况

javascript
// NaN 不等于自己
console.log(NaN === NaN); // false

// 负零和正零相等
console.log(0 === -0); // true
console.log(+0 === -0); // true

// 对象比较仍是比较引用
console.log({} === {}); // false
console.log([] === []); // false

let obj1 = { name: "Alice" };
let obj2 = obj1;
console.log(obj1 === obj2); // true(同一引用)

Object.is()(Same Value 算法)

Object.is() 是 ES6 引入的方法,提供了最严格的等性判断。

基本用法

javascript
// 大多数情况与 === 相同
console.log(Object.is(55)); // true
console.log(Object.is("hello""hello")); // true
console.log(Object.is(truetrue)); // true

console.log(Object.is(5"5")); // false
console.log(Object.is(nullundefined)); // false

// 但在两个特殊情况下不同:

// 1. NaN 等于 NaN
console.log(NaN === NaN); // false
console.log(Object.is(NaNNaN)); // true

// 2. +0 和 -0 不相等
console.log(+0 === -0); // true
console.log(Object.is(+0-0)); // false

Object.is() 的应用场景

javascript
// 场景 1:检查 NaN
function isActualNaN(value) {
  return Object.is(value, NaN);
}

console.log(isActualNaN(NaN)); // true
console.log(isActualNaN("hello")); // false
console.log(isActualNaN(undefined)); // false

// 或者使用 Number.isNaN()
console.log(Number.isNaN(NaN)); // true

// 场景 2:区分 +0 和 -0
function isNegativeZero(value) {
  return Object.is(value, -0);
}

console.log(isNegativeZero(-0)); // true
console.log(isNegativeZero(0)); // false
console.log(isNegativeZero(+0)); // false

// 场景 3:精确比较(避免特殊值的问题)
function strictEqual(ab) {
  return Object.is(a, b);
}

三者对比总结

对比表

javascript
// 比较 NaN
console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
console.log(Object.is(NaNNaN)); // true ✓

// 比较 +0 和 -0
console.log(+0 == -0); // true
console.log(+0 === -0); // true
console.log(Object.is(+0-0)); // false ✓

// 比较 null 和 undefined
console.log(null == undefined); // true ✓
console.log(null === undefined); // false
console.log(Object.is(nullundefined)); // false

// 类型转换
console.log(5 == "5"); // true ✓
console.log(5 === "5"); // false
console.log(Object.is(5"5")); // false

// 普通值比较
console.log(5 == 5); // true
console.log(5 === 5); // true
console.log(Object.is(55)); // true

选择指南

javascript
// 1. 默认使用 ===(99% 的情况)
if (value === expectedValue) {
  // 最安全的选择
}

// 2. 只在检查 null/undefined 时使用 ==
if (value == null) {
  // 检查 null 或 undefined
  // 这是唯一推荐使用 == 的场景
}

// 3. 需要区分 NaN 或 ±0 时使用 Object.is()
if (Object.is(result, NaN)) {
  // 检查是否是 NaN
}

if (Object.is(value, -0)) {
  // 检查是否是负零
}

数组和对象的比较

引用比较

javascript
// 数组和对象的比较是比较引用,不是内容
let arr1 = [123];
let arr2 = [123];
let arr3 = arr1;

console.log(arr1 == arr2); // false(不同引用)
console.log(arr1 === arr2); // false
console.log(arr1 == arr3); // true(同一引用)
console.log(arr1 === arr3); // true

let obj1 = { name: "Alice" };
let obj2 = { name: "Alice" };
let obj3 = obj1;

console.log(obj1 == obj2); // false(不同引用)
console.log(obj1 === obj2); // false
console.log(obj1 === obj3); // true(同一引用)

深度比较

如果需要比较对象或数组的内容,需要自己实现或使用工具库:

javascript
// 简单的深度比较(仅处理基本情况)
function deepEqual(ab) {
  // 相同引用
  if (a === b) return true;

  // 类型不同
  if (typeof a !== typeof b) return false;

  // null 检查
  if (a === null || b === null) return false;

  // 非对象类型
  if (typeof a !== "object") return a === b;

  // 数组
  if (Array.isArray(a) && Array.isArray(b)) {
    if (a.length !== b.length) return false;
    for (let i = 0; i < a.length; i++) {
      if (!deepEqual(a[i], b[i])) return false;
    }
    return true;
  }

  // 对象
  let keysA = Object.keys(a);
  let keysB = Object.keys(b);

  if (keysA.length !== keysB.length) return false;

  for (let key of keysA) {
    if (!keysB.includes(key)) return false;
    if (!deepEqual(a[key], b[key])) return false;
  }

  return true;
}

// 测试
console.log(deepEqual([123], [123])); // true
console.log(deepEqual({ a: 1 }, { a: 1 })); // true
console.log(deepEqual({ a: 1 }, { a: 2 })); // false

// 实际项目中建议使用成熟的库
// 如 Lodash 的 _.isEqual()
// import { isEqual } from 'lodash';
// console.log(isEqual([1, 2], [1, 2]));  // true

常见错误与最佳实践

错误 1:混用 == 和 ===

javascript
// ❌ 不一致
if (value == null) {
  // 使用 ==
  // ...
} else if (value === 0) {
  // 使用 ===
  // ...
}

// ✅ 统一使用 ===(除了检查 null/undefined)
if (value === null || value === undefined) {
  // ...
} else if (value === 0) {
  // ...
}

// 或者一致使用 == null
if (value == null) {
  // ...
} else if (value === 0) {
  // ...
}

错误 2:用 == 比较布尔值

javascript
let hasValue = "true";

// ❌ 错误
if (hasValue == true) {
  // false("true" 转为 NaN)
  console.log("This won't run");
}

// ✅ 正确:利用真值
if (hasValue) {
  // true(非空字符串是真值)
  console.log("This runs");
}

// ✅ 正确:明确比较
if (hasValue === "true") {
  console.log("Explicit check");
}

错误 3:比较 NaN

javascript
let result = 0 / 0; // NaN

// ❌ 错误
if (result == NaN) {
  // 永远是 false
  console.log("This won't work");
}

if (result === NaN) {
  // 永远是 false
  console.log("This won't work either");
}

// ✅ 正确
if (isNaN(result)) {
  // 使用 isNaN()
  console.log("This works");
}

if (Number.isNaN(result)) {
  // 更严格,推荐
  console.log("This is better");
}

if (Object.is(result, NaN)) {
  // 也可以
  console.log("This also works");
}

最佳实践

javascript
// 1. 默认使用 ===
let score = 100;
if (score === 100) {
  console.log("Perfect!");
}

// 2. 检查 null/undefined 时可以用 ==
function processData(data) {
  if (data == null) {
    // 同时检查 null 和 undefined
    return [];
  }
  return data;
}

// 3. 避免与布尔值直接比较,利用真值/假值
let username = "Alice";

// ❌ 不好
if (username === true) {
  // 永远 false
  // ...
}

// ✅ 好
if (username) {
  // 检查是否是真值
  console.log(`Hello, ${username}`);
}

// 4. 比较对象时要小心
let user1 = { name: "Bob" };
let user2 = { name: "Bob" };

// ❌ 这样比较不了内容
if (user1 === user2) {
  // false
  // ...
}

// ✅ 比较特定属性
if (user1.name === user2.name) {
  console.log("Same name");
}

// ✅ 或使用深度比较函数
if (deepEqual(user1, user2)) {
  console.log("Same content");
}

// 5. 使用 ESLint 强制 ===
// .eslintrc.js
// {
//   rules: {
//     'eqeqeq': 'error'  // 强制使用 ===
//   }
// }

特殊值的比较

undefined vs null

javascript
// 两者的区别
let a; // undefined(未赋值)
let b = null; // null(明确赋值为空)

console.log(a == b); // true(特殊规定)
console.log(a === b); // false(类型不同)

// 检查方式
console.log(a === undefined); // true
console.log(b === null); // true

// 同时检查两者
function isEmpty(value) {
  return value == null; // null 或 undefined
}

console.log(isEmpty(null)); // true
console.log(isEmpty(undefined)); // true
console.log(isEmpty(0)); // false
console.log(isEmpty("")); // false

0、-0 和 false

javascript
// 0 和 false
console.log(0 == false); // true
console.log(0 === false); // false

// +0 和 -0
console.log(+0 == -0); // true
console.log(+0 === -0); // true
console.log(Object.is(+0-0)); // false

// 区分正负零的实际应用
function signedZero(value) {
  if (value === 0) {
    return 1 / value === Infinity ? "+0" : "-0";
  }
  return value;
}

console.log(signedZero(+0)); // "+0"
console.log(signedZero(-0)); // "-0"

总结

理解等性比较的细节能帮助你写出更可靠、更少 bug 的代码。

本节要点回顾

  • ==(相等):允许类型转换,规则复杂,容易出错
  • ===(全等):不允许类型转换,行为可预测,推荐使用
  • Object.is():最严格的比较,能正确处理 NaN 和 ±0
  • 默认使用 ===,避免类型转换的陷阱
  • 只在检查 null/undefined 时使用 ==
  • NaN 不等于任何值(包括自己),使用 Number.isNaN() 检查
  • 对象和数组比较的是引用,不是内容
  • 使用 ESLint 的 eqeqeq 规则强制使用 ===
  • 需要深度比较对象时,使用专门的比较函数或库
  • 理解真值/假值,避免不必要的布尔值比较