JavaScript中的对象比较
技术背景
在JavaScript里,直接用 ===
或 ==
比较对象时,比较的是对象的引用,而非对象内容。这就导致即使两个对象的属性和值都相同,使用这些运算符比较也会返回 false
。所以,需要更复杂的方法来比较对象内容是否相同。
实现步骤
1. 快速但有局限的方法
当处理简单的JSON风格对象(无方法和DOM节点)时,可使用 JSON.stringify()
方法:
1
| JSON.stringify(obj1) === JSON.stringify(obj2)
|
不过,这种方法对属性顺序敏感。例如:
1 2 3
| x = {a: 1, b: 2}; y = {b: 2, a: 1}; console.log(JSON.stringify(x) === JSON.stringify(y));
|
2. 慢速但更通用的方法
通过递归比较对象属性和构造函数来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| function deepCompare () { var i, l, leftChain, rightChain;
function compare2Objects (x, y) { var p;
if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') { return true; }
if (x === y) { return true; }
if ((typeof x === 'function' && typeof y === 'function') || (x instanceof Date && y instanceof Date) || (x instanceof RegExp && y instanceof RegExp) || (x instanceof String && y instanceof String) || (x instanceof Number && y instanceof Number)) { return x.toString() === y.toString(); } if (!(x instanceof Object && y instanceof Object)) { return false; }
if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) { return false; }
if (x.constructor !== y.constructor) { return false; } if (x.prototype !== y.prototype) { return false; } if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) { return false; }
for (p in y) { if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) { return false; } else if (typeof y[p] !== typeof x[p]) { return false; } } for (p in x) { if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) { return false; } else if (typeof y[p] !== typeof x[p]) { return false; } switch (typeof (x[p])) { case 'object': case 'function':
leftChain.push(x); rightChain.push(y);
if (!compare2Objects (x[p], y[p])) { return false; }
leftChain.pop(); rightChain.pop(); break; default: if (x[p] !== y[p]) { return false; } break; } } return true; }
if (arguments.length < 1) { return true; }
for (i = 1, l = arguments.length; i < l; i++) {
leftChain = []; rightChain = [];
if (!compare2Objects(arguments[0], arguments[i])) { return false; } }
return true; }
|
3. ES3实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function object_equals( x, y ) { if ( x === y ) return true;
if ( ! ( x instanceof Object ) || ! ( y instanceof Object ) ) return false;
if ( x.constructor !== y.constructor ) return false;
for ( var p in x ) { if ( ! x.hasOwnProperty( p ) ) continue;
if ( ! y.hasOwnProperty( p ) ) return false;
if ( x[ p ] === y[ p ] ) continue;
if ( typeof( x[ p ] ) !== "object" ) return false;
if ( ! object_equals( x[ p ], y[ p ] ) ) return false; } for ( p in y ) if ( y.hasOwnProperty( p ) && ! x.hasOwnProperty( p ) ) return false; return true; }
|
最佳实践
- 简单对象:若对象简单且属性顺序固定,可使用
JSON.stringify()
进行快速比较。 - 复杂对象:对于包含嵌套对象、函数和日期的复杂对象,使用递归比较函数,如
deepCompare
或 object_equals
。
常见问题
- 属性顺序问题:
JSON.stringify()
对属性顺序敏感,若属性顺序不同,比较结果可能为 false
。 - 函数比较:比较函数时,仅比较函数文本可能不准确,因为函数可能有不同的闭包。函数应仅在引用相同时才被视为相等。
- 循环引用:循环引用可能导致无限递归,需在比较时进行处理。例如,在
areEquivalent
函数中使用标记属性避免无限循环。