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(5, 5)); // true
console.log(Object.is(NaN, NaN)); // 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 = [1, 2];
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(5, 5)); // true
console.log(Object.is("hello", "hello")); // true
console.log(Object.is(true, true)); // true
console.log(Object.is(5, "5")); // false
console.log(Object.is(null, undefined)); // false
// 但在两个特殊情况下不同:
// 1. NaN 等于 NaN
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true
// 2. +0 和 -0 不相等
console.log(+0 === -0); // true
console.log(Object.is(+0, -0)); // falseObject.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(a, b) {
return Object.is(a, b);
}三者对比总结
对比表
javascript
// 比较 NaN
console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // 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(null, undefined)); // 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(5, 5)); // 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 = [1, 2, 3];
let arr2 = [1, 2, 3];
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(a, b) {
// 相同引用
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([1, 2, 3], [1, 2, 3])); // 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("")); // false0、-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规则强制使用=== - 需要深度比较对象时,使用专门的比较函数或库
- 理解真值/假值,避免不必要的布尔值比较