JavaScript判斷對象相等性的多種方式
更新時間:2025年12月09日 08:33:10 作者:之恒君
文章總結(jié)了JavaScript中判斷對象相等的多種方法,包括相等性比較運算符、深度比較方法、淺比較、使用第三方庫、ES6+新特性、特殊對象比較以及性能比較,每種方法都有其特點和適用場景,選擇合適的方法可以提高代碼的性能和可維護性,需要的朋友可以參考下
在 JavaScript 中,判斷對象相等有多種方式,各有不同用途和特點。
1. 相等性比較運算符
1.1 嚴格相等===和 寬松相等==
const obj1 = { a: 1 };
const obj2 = { a: 1 };
const obj3 = obj1;
console.log(obj1 === obj2); // false
console.log(obj1 === obj3); // true
console.log(obj1 == obj2); // false
console.log(obj1 == obj3); // true
特點:
- 比較的是引用地址,不是內(nèi)容
- 只有指向同一內(nèi)存地址的對象才相等
- 適用于判斷是否是同一個對象
2. 深度比較方法
2.1 JSON.stringify(簡單對象)
function simpleDeepEqual(obj1, obj2) {
return JSON.stringify(obj1) === JSON.stringify(obj2);
}
const obj1 = { a: 1, b: 2 };
const obj2 = { a: 1, b: 2 };
const obj3 = { b: 2, a: 1 }; // 屬性順序不同
const obj4 = { a: 1, b: null };
console.log(simpleDeepEqual(obj1, obj2)); // true
console.log(simpleDeepEqual(obj1, obj3)); // false!順序不同
console.log(simpleDeepEqual(obj1, { a: 1, b: 2 })); // true
局限性:
- 屬性順序敏感
- 不能處理
undefined、function、Symbol、循環(huán)引用 undefined會被忽略,NaN會變成null- 日期對象會轉(zhuǎn)為字符串
// JSON.stringify 的問題
console.log(JSON.stringify({ a: undefined })); // "{}"
console.log(JSON.stringify({ a: function() {} })); // "{}"
console.log(JSON.stringify({ a: Symbol() })); // "{}"
console.log(JSON.stringify({ a: NaN })); // '{"a":null}'
console.log(JSON.stringify(new Date())); // 日期字符串
2.2 遞歸深度比較
function deepEqual(obj1, obj2) {
// 1. 基本類型比較
if (obj1 === obj2) return true;
// 2. 檢查 null
if (obj1 == null || obj2 == null) return false;
// 3. 檢查類型
if (typeof obj1 !== typeof obj2) return false;
if (obj1.constructor !== obj2.constructor) return false;
// 4. 特殊類型處理
if (obj1 instanceof Date && obj2 instanceof Date) {
return obj1.getTime() === obj2.getTime();
}
if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
return obj1.toString() === obj2.toString();
}
// 5. 數(shù)組比較
if (Array.isArray(obj1)) {
if (obj1.length !== obj2.length) return false;
for (let i = 0; i < obj1.length; i++) {
if (!deepEqual(obj1[i], obj2[i])) return false;
}
return true;
}
// 6. 對象比較
if (typeof obj1 === 'object') {
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (let key of keys1) {
if (!obj2.hasOwnProperty(key)) return false;
if (!deepEqual(obj1[key], obj2[key])) return false;
}
return true;
}
// 7. 其他類型
return false;
}
優(yōu)化版本:
function deepEqual(obj1, obj2, visited = new WeakMap()) {
// 處理循環(huán)引用
if (visited.has(obj1) && visited.get(obj1) === obj2) {
return true;
}
visited.set(obj1, obj2);
// 基本類型
if (obj1 === obj2) return true;
if (obj1 == null || obj2 == null) return false;
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') {
return obj1 === obj2;
}
// 不同類型
if (obj1.constructor !== obj2.constructor) return false;
// 特殊對象
if (obj1 instanceof Date) return obj1.getTime() === obj2.getTime();
if (obj1 instanceof RegExp) return obj1.toString() === obj2.toString();
if (obj1 instanceof Map || obj2 instanceof Map) {
if (obj1.size !== obj2.size) return false;
for (let [key, val] of obj1) {
if (!obj2.has(key)) return false;
if (!deepEqual(val, obj2.get(key), visited)) return false;
}
return true;
}
if (obj1 instanceof Set || obj2 instanceof Set) {
if (obj1.size !== obj2.size) return false;
for (let val of obj1) {
if (!obj2.has(val)) return false;
}
return true;
}
// 普通對象/數(shù)組
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (let key of keys1) {
if (!obj2.hasOwnProperty(key)) return false;
if (!deepEqual(obj1[key], obj2[key], visited)) return false;
}
return true;
}
3. 淺比較
3.1 淺比較(只比較第一層)
function shallowEqual(obj1, obj2) {
if (obj1 === obj2) return true;
if (typeof obj1 !== 'object' || obj1 === null ||
typeof obj2 !== 'object' || obj2 === null) {
return false;
}
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (let key of keys1) {
if (!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]) {
return false;
}
}
return true;
}
// 使用
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { a: 1, b: { c: 2 } };
console.log(shallowEqual(obj1, obj2)); // false(b 屬性引用不同)
console.log(shallowEqual(obj1, obj1)); // true
3.2 React 風(fēng)格的淺比較
function reactShallowEqual(objA, objB) {
if (Object.is(objA, objB)) return true;
if (typeof objA !== 'object' || objA === null ||
typeof objB !== 'object' || objB === null) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) return false;
for (let i = 0; i < keysA.length; i++) {
if (
!Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
!Object.is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}
4. 使用第三方庫
4.1 Lodash
// 安裝: npm install lodash
import _ from 'lodash';
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { a: 1, b: { c: 2 } };
console.log(_.isEqual(obj1, obj2)); // true
console.log(_.isEqualWith(obj1, obj2, customizer)); // 自定義比較
4.2 deep-diff
// 安裝: npm install deep-diff
import diff from 'deep-diff';
const differences = diff(obj1, obj2);
if (!differences) {
console.log('對象相同');
} else {
console.log('差異:', differences);
}
5. ES6+ 新特性比較
5.1 Object.is
// Object.is 用于比較值
console.log(Object.is(0, -0)); // false
console.log(Object.is(NaN, NaN)); // true
console.log(Object.is({}, {})); // false(引用比較)
// 與 === 的區(qū)別
console.log(+0 === -0); // true
console.log(NaN === NaN); // false
console.log(Object.is(+0, -0)); // false
console.log(Object.is(NaN, NaN)); // true
5.2 Map 和 Set
// Set 自動去重 const set1 = new Set([1, 2, 3]); const set2 = new Set([1, 2, 3]); console.log(set1 === set2); // false console.log(_.isEqual(set1, set2)); // true // Map const map1 = new Map([['a', 1]]); const map2 = new Map([['a', 1]]); console.log(map1 === map2); // false console.log(_.isEqual(map1, map2)); // true
6. 特殊對象比較
6.1 函數(shù)比較
function func1() { return 1; }
function func2() { return 1; }
function func3 = func1;
console.log(func1 === func2); // false
console.log(func1 === func3); // true
console.log(func1.toString() === func2.toString()); // true(不推薦)
// 函數(shù)比較實用方法
function functionsEqual(fn1, fn2) {
return fn1.toString() === fn2.toString();
}
6.2 類實例比較
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
equals(other) {
if (!(other instanceof Person)) return false;
return this.name === other.name && this.age === other.age;
}
}
const p1 = new Person('Alice', 30);
const p2 = new Person('Alice', 30);
const p3 = new Person('Bob', 25);
console.log(p1 === p2); // false
console.log(p1.equals(p2)); // true
console.log(p1.equals(p3)); // false
7. 性能比較
function performanceTest() {
const obj1 = { a: 1, b: 2, c: { d: 3, e: { f: 4 } } };
const obj2 = { a: 1, b: 2, c: { d: 3, e: { f: 4 } } };
const obj3 = { a: 1, b: 2, c: { d: 3, e: { f: 5 } } };
const iterations = 10000;
// 1. JSON.stringify
console.time('JSON.stringify');
for (let i = 0; i < iterations; i++) {
JSON.stringify(obj1) === JSON.stringify(obj2);
}
console.timeEnd('JSON.stringify');
// 2. 遞歸深度比較
console.time('遞歸深度比較');
for (let i = 0; i < iterations; i++) {
deepEqual(obj1, obj2);
}
console.timeEnd('遞歸深度比較');
// 3. Lodash
console.time('Lodash');
for (let i = 0; i < iterations; i++) {
_.isEqual(obj1, obj2);
}
console.timeEnd('Lodash');
// 4. 淺比較
console.time('淺比較');
for (let i = 0; i < iterations; i++) {
shallowEqual(obj1, obj2);
}
console.timeEnd('淺比較');
}
// 結(jié)果通常:
// 淺比較最快
// JSON.stringify 次之
// Lodash 再次
// 遞歸深度比較最慢
8. 實用工具函數(shù)
8.1 通用深度比較
function isDeepEqual(obj1, obj2, options = {}) {
const {
strict = true, // 嚴格比較
checkSymbols = false, // 檢查 Symbol
skipKeys = [], // 跳過的鍵
} = options;
// 快速路徑
if (obj1 === obj2) return true;
if (obj1 == null || obj2 == null) return false;
const type1 = typeof obj1;
const type2 = typeof obj2;
if (type1 !== type2) return false;
// 基本類型
if (type1 !== 'object' && type1 !== 'function') {
if (strict) {
return Object.is(obj1, obj2);
}
return obj1 == obj2;
}
// 構(gòu)造函數(shù)比較
if (obj1.constructor !== obj2.constructor) return false;
// 特殊對象處理
if (obj1 instanceof Date) {
return obj1.getTime() === obj2.getTime();
}
if (obj1 instanceof RegExp) {
return obj1.toString() === obj2.toString();
}
if (obj1 instanceof Map) {
if (obj1.size !== obj2.size) return false;
for (let [key, val] of obj1) {
if (!obj2.has(key) || !isDeepEqual(val, obj2.get(key), options)) {
return false;
}
}
return true;
}
if (obj1 instanceof Set) {
if (obj1.size !== obj2.size) return false;
for (let val of obj1) {
if (!obj2.has(val)) return false;
}
return true;
}
// 收集所有鍵
const keys1 = [...Object.keys(obj1)];
const keys2 = [...Object.keys(obj2)];
if (checkSymbols) {
keys1.push(...Object.getOwnPropertySymbols(obj1));
keys2.push(...Object.getOwnPropertySymbols(obj2));
}
// 過濾跳過的鍵
const filteredKeys1 = keys1.filter(key => !skipKeys.includes(key));
const filteredKeys2 = keys2.filter(key => !skipKeys.includes(key));
if (filteredKeys1.length !== filteredKeys2.length) return false;
for (let key of filteredKeys1) {
if (!obj2.hasOwnProperty(key)) return false;
if (!isDeepEqual(obj1[key], obj2[key], options)) return false;
}
return true;
}
8.2 比較指定路徑
function compareByPath(obj1, obj2, paths) {
for (let path of paths) {
const val1 = get(obj1, path);
const val2 = get(obj2, path);
if (!isDeepEqual(val1, val2)) {
return false;
}
}
return true;
}
function get(obj, path) {
return path.split('.').reduce((acc, key) => acc && acc[key], obj);
}
// 使用
const obj1 = { a: 1, b: { c: 2, d: 3 }, e: 4 };
const obj2 = { a: 1, b: { c: 2, d: 5 }, e: 4 };
console.log(compareByPath(obj1, obj2, ['a', 'b.c'])); // true
console.log(compareByPath(obj1, obj2, ['a', 'b.d'])); // false
9. 常見場景
9.1 React/PureComponent
// React 的 shouldComponentUpdate
class MyComponent extends React.PureComponent {
// 自動進行淺比較
}
// 自定義 shouldComponentUpdate
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return !shallowEqual(this.props, nextProps) ||
!shallowEqual(this.state, nextState);
}
}
9.2 Redux
// Redux reducer
function reducer(state = initialState, action) {
switch (action.type) {
case 'UPDATE_DATA':
// 深度比較
if (_.isEqual(state.data, action.data)) {
return state; // 相同,不更新
}
return { ...state, data: action.data };
default:
return state;
}
}
9.3 緩存(Memoization)
// 使用深度比較的緩存
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
for (let [cachedKey, cachedValue] of cache) {
if (isDeepEqual(JSON.parse(cachedKey), args)) {
return cachedValue;
}
}
const result = fn(...args);
cache.set(key, result);
return result;
};
}
10. 最佳實踐總結(jié)
| 場景 | 推薦方法 | 原因 |
|---|---|---|
| 比較引用是否相同 | ===或 Object.is | 最快,最直接 |
| 簡單對象深度比較 | JSON.stringify | 簡單,但有限制 |
| 復(fù)雜對象深度比較 | Lodash 的 _.isEqual | 可靠,功能完整 |
| 性能關(guān)鍵,比較第一層 | 淺比較 | 最快,適合不可變數(shù)據(jù) |
| 自定義比較邏輯 | 自定義比較函數(shù) | 靈活可控 |
| 比較類實例 | 實現(xiàn) equals方法 | 面向?qū)ο?,語義清晰 |
選擇指南:
- 引用比較用
===,值比較用深度比較 - 性能優(yōu)先用淺比較,精度優(yōu)先用深度比較
- 生產(chǎn)環(huán)境用 Lodash,簡單場景用 JSON.stringify
- 特殊對象要特殊處理(Date、RegExp、Map、Set 等)
- 注意邊界情況(循環(huán)引用、Symbol、undefined 等)
根據(jù)具體需求選擇合適的方法,平衡性能、準確性和可維護性。
以上就是JavaScript判斷對象相等性的多種方式的詳細內(nèi)容,更多關(guān)于JavaScript對象相等性判斷的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
javascript實現(xiàn)自動輸出文本(打字特效)
文字如何實現(xiàn)打字的效果呢?在瀏覽網(wǎng)頁的時候也經(jīng)常能看到這種效果。本文給大家匯總介紹了幾種打字效果的文字特效,文字一個一個地打印在頁面上。2015-08-08
javascript自執(zhí)行函數(shù)之偽命名空間封裝法
比較之后,我們可以發(fā)現(xiàn),第二方法更加的直觀,易于理解。但是少了封裝過程,代碼完全裸露在外。2010-12-12
JavaScript實現(xiàn)的仿新浪微博原生態(tài)輸入字數(shù)即時檢查功能【兼容IE6】
這篇文章主要介紹了JavaScript實現(xiàn)的仿新浪微博原生態(tài)輸入字數(shù)即時檢查功能,涉及javascript事件響應(yīng)及字符串的遍歷、轉(zhuǎn)換、判斷等相關(guān)操作技巧,需要的朋友可以參考下2017-09-09

