JavaScript中的对象比较[重复]
Posted
技术标签:
【中文标题】JavaScript中的对象比较[重复]【英文标题】:Object comparison in JavaScript [duplicate] 【发布时间】:2019-01-28 01:22:19 【问题描述】:在 javascript 中比较对象的最佳方法是什么?
例子:
var user1 = name : "nerd", org: "dev";
var user2 = name : "nerd", org: "dev";
var eq = user1 == user2;
alert(eq); // gives false
我知道如果两个对象引用完全相同的对象,则它们是相等的,但是有没有办法检查它们是否具有相同的属性值?
以下方法对我有用,但这是唯一的可能性吗?
var eq = Object.toJSON(user1) == Object.toJSON(user2);
alert(eq); // gives true
【问题讨论】:
我对这个话题做了一些修改,并为这个问题设计了一个灵活的解决方案stamat.wordpress.com/2013/06/22/javascript-object-comparison 测试(深度)相等性是一件很难做到的事情。前往github.com/loveencounterflow/jseq 查看流行的equal()
实现的测试套件,它已经涵盖了许多边缘情况。文档中的讨论也很透彻。
使用 lodash。 isEqual
方法正是你想要的。
使用facebook.github.io/immutable-js,这个操作会超级简单快速
使用下划线,_.isEqual( obj1 , obj2 )
【参考方案1】:
不幸的是,没有完美的方法,除非您递归地使用 _proto_
并访问所有不可枚举的属性,但这仅适用于 Firefox。
所以我能做的最好的就是猜测使用场景。
1) 快速且有限。
当您有简单的 JSON 样式的对象时工作,其中没有方法和 DOM 节点:
JSON.stringify(obj1) === JSON.stringify(obj2)
属性的顺序很重要,因此此方法将为以下对象返回 false:
x = a: 1, b: 2;
y = b: 2, a: 1;
2) 速度慢且更通用。
在不深入原型的情况下比较对象,然后递归比较属性的投影,并比较构造函数。
这几乎是正确的算法:
function deepCompare ()
var i, l, leftChain, rightChain;
function compare2Objects (x, y)
var p;
// remember that NaN === NaN returns false
// and isNaN(undefined) returns true
if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number')
return true;
// Compare primitives and functions.
// Check if both arguments link to the same object.
// Especially useful on the step where we compare prototypes
if (x === y)
return true;
// Works in case when functions are created in constructor.
// Comparing dates is a common scenario. Another built-ins?
// We can even handle functions passed across iframes
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();
// At last checking prototypes as good as we can
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;
// Check for infinitive linking loops
if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1)
return false;
// Quick checking of one object being a subset of another.
// todo: cache the structure of arguments[0] for performance
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; //Die silently? Don't know how to handle such case, please help...
// throw "Need two or more arguments to compare";
for (i = 1, l = arguments.length; i < l; i++)
leftChain = []; //Todo: this can be cached
rightChain = [];
if (!compare2Objects(arguments[0], arguments[i]))
return false;
return true;
已知问题(嗯,它们的优先级很低,可能你永远不会注意到它们):
原型结构不同但投影相同的对象 函数可能具有相同的文本,但引用不同的闭包测试:通过测试来自How to determine equality for two JavaScript objects?。
【讨论】:
当属性已定义但设置为undefined
值时,对 undefined
的检查将失败。使用in
运算符而不是typeof
来避免这种情况:p in x
。通过字符串值比较函数也非常不可靠。除了函数分解失败的常见原因之外,由于闭包导致两个函数具有相同的代码但行为非常不同的情况也很常见。例如。任何由 jQuery 的 $.proxy
或 Prototype 的 Function#bind
创建的函数。我只是坚持比较函数身份。
我认为你应该使用 identical 比较运算符:===
,因为 a: 5
和 a: "5.0"
不相等,或者它们不相等?
如果可以避免,您不应该扩展 Object.prototype。如果该循环内没有if(!someObject.hasOwnProperty(key)) continue;
,则会导致诸如破坏for(var key in someObject)
之类的丑陋问题。
函数比较是错误的:函数可能有相同的文本但引用不同的闭包。最好只返回this[p] === x[p]
。
关于1) “属性的顺序很重要,所以这个方法会为以下对象返回false:”这不一定是真的。该方法可能为这些对象返回false
,也可能不返回。无论哪种方式都无法保证。这就是为什么我们不使用JSON.stringify
比较进行对象比较。无法保证顺序。【参考方案2】:
这是我的 ES3 注释解决方案(代码后有血淋淋的细节):
function object_equals( x, y )
if ( x === y ) return true;
// if both x and y are null or undefined and exactly the same
if ( ! ( x instanceof Object ) || ! ( y instanceof Object ) ) return false;
// if they are not strictly equal, they both need to be Objects
if ( x.constructor !== y.constructor ) return false;
// they must have the exact same prototype chain, the closest we can do is
// test there constructor.
for ( var p in x )
if ( ! x.hasOwnProperty( p ) ) continue;
// other properties were tested using x.constructor === y.constructor
if ( ! y.hasOwnProperty( p ) ) return false;
// allows to compare x[ p ] and y[ p ] when set to undefined
if ( x[ p ] === y[ p ] ) continue;
// if they have the same strict value or identity then they are equal
if ( typeof( x[ p ] ) !== "object" ) return false;
// Numbers, Strings, Functions, Booleans must be strictly equal
if ( ! object_equals( x[ p ], y[ p ] ) ) return false;
// Objects and Arrays must be tested recursively
for ( p in y )
if ( y.hasOwnProperty( p ) && ! x.hasOwnProperty( p ) )
return false;
// allows x[ p ] to be set to undefined
return true;
在开发此解决方案时,我特别关注了极端情况和效率,但试图产生一个可行的简单解决方案,并希望具有一些优雅。 JavaScript 允许 null 和 undefined 属性,并且对象具有 原型链 如果不加以检查,可能会导致非常不同的行为。
首先我选择不扩展 Object.prototype,主要是因为 null 不能是其中之一比较的对象,并且我认为 null 应该是与另一个比较的有效对象。对于 Object.prototype 的扩展,其他人还注意到其他合理的担忧,可能会对其他人的代码产生副作用。
必须特别注意处理 JavaScript 允许将对象属性设置为 undefined 的可能性,即存在值设置为 未定义。上述解决方案验证两个对象是否具有设置为 undefined 以报告相等性的相同属性。这只能通过使用 Object.hasOwnProperty(property_name) 检查属性的存在来完成。另请注意,JSON.stringify() 会删除设置为 undefined 的属性,因此使用此方法进行比较form 将忽略设置为值 undefined 的属性。
只有当函数共享相同的引用而不是相同的代码时,它们才应该被认为是相等的,因为这不会考虑这些函数原型。因此比较代码字符串并不能保证它们具有相同的原型对象。
这两个对象应该具有相同的原型链,而不仅仅是相同的属性。这只能通过比较两个对象的 构造函数 的严格相等性来测试跨浏览器。 ECMAScript 5 将允许使用 Object.getPrototypeOf() 测试他们的实际原型。一些网络浏览器还提供了一个 __proto__ 属性来做同样的事情。对上述代码的可能改进将允许在可用时使用其中一种方法。
这里使用严格比较是最重要的,因为 2 不应被视为等于 "2.0000" , 也不应将 false 视为等于 null, undefined,或 0。
效率考虑使我尽快比较属性的相等性。然后,仅当失败时,才查找这些属性的 typeof。对于具有大量标量属性的大型对象,速度提升可能非常显着。
不再需要两个循环,第一个检查左侧对象的属性,第二个检查右侧的属性并验证是否存在(而不是值),以捕获使用 定义的这些属性未定义值。
总的来说,这段代码只用了 16 行代码(没有 cmets)就处理了大多数极端情况。
更新(2015 年 8 月 13 日)。我已经实现了一个更好的版本,因为函数 value_equals() 更快,可以正确处理诸如 NaN 和不同于 -0 的 0 之类的极端情况,可以选择强制执行对象的属性顺序和测试循环引用,支持超过 100 automated tests作为Toubkal 项目测试套件的一部分。
【讨论】:
【参考方案3】: Utils.compareObjects = function(o1, o2)
for(var p in o1)
if(o1.hasOwnProperty(p))
if(o1[p] !== o2[p])
return false;
for(var p in o2)
if(o2.hasOwnProperty(p))
if(o1[p] !== o2[p])
return false;
return true;
;
比较 ONE-LEVEL 对象的简单方法。
【讨论】:
【参考方案4】:当然不是唯一的方法 - 您可以创建一个方法的原型(此处针对 Object,但我当然不建议将 Object 用于实时代码)来复制 C#/Java 风格的比较方法。
编辑,因为似乎需要一个通用示例:
Object.prototype.equals = function(x)
for(p in this)
switch(typeof(this[p]))
case 'object':
if (!this[p].equals(x[p])) return false ; break;
case 'function':
if (typeof(x[p])=='undefined' || (p != 'equals' && this[p].toString() != x[p].toString())) return false; ; break;
default:
if (this[p] != x[p]) return false;
for(p in x)
if(typeof(this[p])=='undefined') return false;
return true;
请注意,使用 toString() 测试方法绝对不够好,但由于空格是否有意义的问题,可接受的方法非常困难,更不用说同义词方法和方法用不同的实现产生相同的结果。 以及一般来说针对 Object 进行原型设计的问题。
【讨论】:
【参考方案5】:以下算法将处理自引用数据结构、数字、字符串、日期,当然还有普通的嵌套 javascript 对象:
对象被认为是等价的
根据===
,它们完全相等(首先解开字符串和数字以确保42
等同于Number(42)
)
或者它们都是日期并且具有相同的valueOf()
或者它们都是相同类型且不为空且...
它们不是对象,根据==
相等(捕获数字/字符串/布尔值)
或者,忽略具有undefined
值的属性,它们具有相同的属性,所有这些属性都被视为递归等效。
函数不被函数文本视为相同。这个测试是不够的,因为函数可能有不同的闭包。只有当===
这么说时,函数才被认为是相等的(但如果您选择这样做,您可以轻松地扩展该等价关系)。
无限循环。当areEquivalent
试图反驳相等性并递归到对象的属性中这样做时,它会跟踪需要此子比较的对象。如果相等性可以被证伪,那么对象之间的某些可达属性路径不同,那么这样的可达路径必定存在最短,并且该最短可达路径不能包含两条路径中都存在的环;即递归比较对象时可以假设相等。假设存储在属性areEquivalent_Eq_91_2_34
中,使用后删除,但如果对象图已经包含这样的属性,则行为未定义。使用这种标记属性是必要的,因为 javascript 不支持使用任意对象作为键的字典。
function unwrapStringOrNumber(obj)
return (obj instanceof Number || obj instanceof String
? obj.valueOf()
: obj);
function areEquivalent(a, b)
a = unwrapStringOrNumber(a);
b = unwrapStringOrNumber(b);
if (a === b) return true; //e.g. a and b both null
if (a === null || b === null || typeof (a) !== typeof (b)) return false;
if (a instanceof Date)
return b instanceof Date && a.valueOf() === b.valueOf();
if (typeof (a) !== "object")
return a == b; //for boolean, number, string, xml
var newA = (a.areEquivalent_Eq_91_2_34 === undefined),
newB = (b.areEquivalent_Eq_91_2_34 === undefined);
try
if (newA) a.areEquivalent_Eq_91_2_34 = [];
else if (a.areEquivalent_Eq_91_2_34.some(
function (other) return other === b; )) return true;
if (newB) b.areEquivalent_Eq_91_2_34 = [];
else if (b.areEquivalent_Eq_91_2_34.some(
function (other) return other === a; )) return true;
a.areEquivalent_Eq_91_2_34.push(b);
b.areEquivalent_Eq_91_2_34.push(a);
var tmp = ;
for (var prop in a)
if(prop != "areEquivalent_Eq_91_2_34")
tmp[prop] = null;
for (var prop in b)
if (prop != "areEquivalent_Eq_91_2_34")
tmp[prop] = null;
for (var prop in tmp)
if (!areEquivalent(a[prop], b[prop]))
return false;
return true;
finally
if (newA) delete a.areEquivalent_Eq_91_2_34;
if (newB) delete b.areEquivalent_Eq_91_2_34;
【讨论】:
【参考方案6】:我为对象比较编写了这段代码,它似乎可以工作。检查断言:
function countProps(obj)
var count = 0;
for (k in obj)
if (obj.hasOwnProperty(k))
count++;
return count;
;
function objectEquals(v1, v2)
if (typeof(v1) !== typeof(v2))
return false;
if (typeof(v1) === "function")
return v1.toString() === v2.toString();
if (v1 instanceof Object && v2 instanceof Object)
if (countProps(v1) !== countProps(v2))
return false;
var r = true;
for (k in v1)
r = objectEquals(v1[k], v2[k]);
if (!r)
return false;
return true;
else
return v1 === v2;
assert.isTrue(objectEquals(null,null));
assert.isFalse(objectEquals(null,undefined));
assert.isTrue(objectEquals("hi","hi"));
assert.isTrue(objectEquals(5,5));
assert.isFalse(objectEquals(5,10));
assert.isTrue(objectEquals([],[]));
assert.isTrue(objectEquals([1,2],[1,2]));
assert.isFalse(objectEquals([1,2],[2,1]));
assert.isFalse(objectEquals([1,2],[1,2,3]));
assert.isTrue(objectEquals(,));
assert.isTrue(objectEquals(a:1,b:2,a:1,b:2));
assert.isTrue(objectEquals(a:1,b:2,b:2,a:1));
assert.isFalse(objectEquals(a:1,b:2,a:1,b:3));
assert.isTrue(objectEquals(1:name:"mhc",age:28, 2:name:"arb",age:26,1:name:"mhc",age:28, 2:name:"arb",age:26));
assert.isFalse(objectEquals(1:name:"mhc",age:28, 2:name:"arb",age:26,1:name:"mhc",age:28, 2:name:"arb",age:27));
assert.isTrue(objectEquals(function(x)return x;,function(x)return x;));
assert.isFalse(objectEquals(function(x)return x;,function(y)return y+2;));
【讨论】:
将 let befor k 放入 for 循环导致错误【参考方案7】:我对上面的代码做了一些修改。对我来说 0 !== false 和 null !== undefined。如果您不需要如此严格的检查,请在代码中的“this[p] !== x[p]”中删除一个“=”符号。
Object.prototype.equals = function(x)
for (var p in this)
if(typeof(this[p]) !== typeof(x[p])) return false;
if((this[p]===null) !== (x[p]===null)) return false;
switch (typeof(this[p]))
case 'undefined':
if (typeof(x[p]) != 'undefined') return false;
break;
case 'object':
if(this[p]!==null && x[p]!==null && (this[p].constructor.toString() !== x[p].constructor.toString() || !this[p].equals(x[p]))) return false;
break;
case 'function':
if (p != 'equals' && this[p].toString() != x[p].toString()) return false;
break;
default:
if (this[p] !== x[p]) return false;
return true;
然后我用下一个对象对其进行了测试:
var a = a: 'text', b:[0,1];
var b = a: 'text', b:[0,1];
var c = a: 'text', b: 0;
var d = a: 'text', b: false;
var e = a: 'text', b:[1,0];
var f = a: 'text', b:[1,0], f: function() this.f = this.b; ;
var g = a: 'text', b:[1,0], f: function() this.f = this.b; ;
var h = a: 'text', b:[1,0], f: function() this.a = this.b; ;
var i =
a: 'text',
c:
b: [1, 0],
f: function()
this.a = this.b;
;
var j =
a: 'text',
c:
b: [1, 0],
f: function()
this.a = this.b;
;
var k = a: 'text', b: null;
var l = a: 'text', b: undefined;
a==b 预期为真;返回真
a==c 预期为假;返回错误
c==d 预期为假;返回错误
a==e 预期为假;返回错误
f==g 预期为真;返回真
h==g 预期为假;返回错误
i==j 预期为真;返回真
d==k 预期为假;返回错误
k==l 预期为假;返回错误
【讨论】:
+1 用于测试构造函数或子对象。但是为什么不测试主要对象呢?为什么不通过引用来测试函数,而不是使用 toString() 比较字符串,这既慢又不准确。【参考方案8】:这是我的版本,该线程中的几乎所有内容都已集成(测试用例的数量相同):
Object.defineProperty(Object.prototype, "equals",
enumerable: false,
value: function (obj)
var p;
if (this === obj)
return true;
// some checks for native types first
// function and sring
if (typeof(this) === "function" || typeof(this) === "string" || this instanceof String)
return this.toString() === obj.toString();
// number
if (this instanceof Number || typeof(this) === "number")
if (obj instanceof Number || typeof(obj) === "number")
return this.valueOf() === obj.valueOf();
return false;
// null.equals(null) and undefined.equals(undefined) do not inherit from the
// Object.prototype so we can return false when they are passed as obj
if (typeof(this) !== typeof(obj) || obj === null || typeof(obj) === "undefined")
return false;
function sort (o)
var result = ;
if (typeof o !== "object")
return o;
Object.keys(o).sort().forEach(function (key)
result[key] = sort(o[key]);
);
return result;
if (typeof(this) === "object")
if (Array.isArray(this)) // check on arrays
return JSON.stringify(this) === JSON.stringify(obj);
else // anyway objects
for (p in this)
if (typeof(this[p]) !== typeof(obj[p]))
return false;
if ((this[p] === null) !== (obj[p] === null))
return false;
switch (typeof(this[p]))
case 'undefined':
if (typeof(obj[p]) !== 'undefined')
return false;
break;
case 'object':
if (this[p] !== null
&& obj[p] !== null
&& (this[p].constructor.toString() !== obj[p].constructor.toString()
|| !this[p].equals(obj[p])))
return false;
break;
case 'function':
if (this[p].toString() !== obj[p].toString())
return false;
break;
default:
if (this[p] !== obj[p])
return false;
;
// at least check them with JSON
return JSON.stringify(sort(this)) === JSON.stringify(sort(obj));
);
这是我的测试用例:
assertFalse(.equals(null));
assertFalse(.equals(undefined));
assertTrue("String", "hi".equals("hi"));
assertTrue("Number", new Number(5).equals(5));
assertFalse("Number", new Number(5).equals(10));
assertFalse("Number+String", new Number(1).equals("1"));
assertTrue([].equals([]));
assertTrue([1,2].equals([1,2]));
assertFalse([1,2].equals([2,1]));
assertFalse([1,2].equals([1,2,3]));
assertTrue(new Date("2011-03-31").equals(new Date("2011-03-31")));
assertFalse(new Date("2011-03-31").equals(new Date("1970-01-01")));
assertTrue(.equals());
assertTrue(a:1,b:2.equals(a:1,b:2));
assertTrue(a:1,b:2.equals(b:2,a:1));
assertFalse(a:1,b:2.equals(a:1,b:3));
assertTrue(1:name:"mhc",age:28, 2:name:"arb",age:26.equals(1:name:"mhc",age:28, 2:name:"arb",age:26));
assertFalse(1:name:"mhc",age:28, 2:name:"arb",age:26.equals(1:name:"mhc",age:28, 2:name:"arb",age:27));
assertTrue("Function", (function(x)return x;).equals(function(x)return x;));
assertFalse("Function", (function(x)return x;).equals(function(y)return y+2;));
var a = a: 'text', b:[0,1];
var b = a: 'text', b:[0,1];
var c = a: 'text', b: 0;
var d = a: 'text', b: false;
var e = a: 'text', b:[1,0];
var f = a: 'text', b:[1,0], f: function() this.f = this.b; ;
var g = a: 'text', b:[1,0], f: function() this.f = this.b; ;
var h = a: 'text', b:[1,0], f: function() this.a = this.b; ;
var i =
a: 'text',
c:
b: [1, 0],
f: function()
this.a = this.b;
;
var j =
a: 'text',
c:
b: [1, 0],
f: function()
this.a = this.b;
;
var k = a: 'text', b: null;
var l = a: 'text', b: undefined;
assertTrue(a.equals(b));
assertFalse(a.equals(c));
assertFalse(c.equals(d));
assertFalse(a.equals(e));
assertTrue(f.equals(g));
assertFalse(h.equals(g));
assertTrue(i.equals(j));
assertFalse(d.equals(k));
assertFalse(k.equals(l));
【讨论】:
JSON.stringify() 删除设置为未定义的属性,因此使用此表单的比较将忽略设置为未定义值的属性:assertFalse([1,2,null].equals([ 1,2,未定义]))。 你正在对数组进行字符串化,但是数组里面可以有复杂的对象 这个测试不应该断言 false 而不是 true,因为一个是 Object 的实例而另一个是原始的? assertTrue("Number", new Number(5).equals(5));【参考方案9】:如果你想明确地检查方法,你可以使用 method.toSource() 或 method.toString() 方法。
【讨论】:
由于我描述的原因真的不够好 所以你会遍历对象的元素,检查类型是什么,然后在找到函数时使用 toSource() 或 toString()? 诺斯雷德娜,是的。这会给你函数的实际文本。 anakata,我不明白什么不够好以及你实际上想要做什么。你能详细说明一下吗? @snz3 - 空格、分号和大括号以及类似的语法差异存在严重问题,这些差异可能会或可能不会产生影响,并且如果不解析(即从原始字符串解耦)就很难确定格式。还有波动状态和原型设计的问题。基本上字符串在捕获两个对象的状态方面不够好。 您好,从 2021 年开始,toSource
已弃用,请勿使用【参考方案10】:
如果您在没有 JSON 库的情况下工作,也许这会对您有所帮助:
Object.prototype.equals = function(b)
var a = this;
for(i in a)
if(typeof b[i] == 'undefined')
return false;
if(typeof b[i] == 'object')
if(!b[i].equals(a[i]))
return false;
if(b[i] != a[i])
return false;
for(i in b)
if(typeof a[i] == 'undefined')
return false;
if(typeof a[i] == 'object')
if(!a[i].equals(b[i]))
return false;
if(a[i] != b[i])
return false;
return true;
var a = foo:'bar', bar: blub:'bla';
var b = foo:'bar', bar: blub:'blob';
alert(a.equals(b)); // alert's a false
【讨论】:
这是个坏主意。修改 Object.prototype 可能会产生各种无法预料的后果。 没有 JSON 库就无法工作,它是 Javascript 标准库的一部分:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…以上是关于JavaScript中的对象比较[重复]的主要内容,如果未能解决你的问题,请参考以下文章
将本机 javascript 对象与 jQuery 进行比较 [重复]
javascript中innerHTML和appendChild的比较