Сравнение объектов в JavaScript [дубликат]
этот вопрос уже есть ответ здесь:
каков наилучший способ сравнения объектов в 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
10 ответов:
к сожалению, нет идеального способ, если вы не используете
_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; }известные проблемы (ну, они имеют очень низкий приоритет, вероятно, вы никогда не будете замечать их):
- объекты с различной структурой прототипа, но же проекция
- функции могут иметь одинаковый текст, но относятся к разным закрытиям
тесты: проходит тесты от как определить равенство для двух объектов JavaScript?.
вот мое прокомментированное решение в ES3 (кровавые детали после кода):
Object.equals = function( 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 и неопределено свойства и объекты цепи прототипов это может привести к очень разным поведениям, если не проверять.
сначала я решил продлить объект вместо
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; };простой способ сравнить только одноуровневые объекты.
конечно, не единственный способ-вы можете создать прототип метода (против объекта здесь, но я, конечно, не предложил бы использовать объект для живого кода) для репликации методов сравнения стилей 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 () - это абсолютно не достаточно хорошо но метод, который был бы приемлем, очень сложен из-за проблемы пробелов, имеющих значение или нет, никогда методы синонима ума и методы, производящие тот же результат с различными реализациями. и проблемы прототипирования против объекта в целом.
следующий алгоритм будет иметь дело с самореферентными структурами данных, числами, строками, датами и, конечно же, простыми вложенными объектами javascript:
объекты считаются эквивалентными, когда
- они точно равны в
===(строка и число разворачиваются сначала, чтобы обеспечить42эквивалентноNumber(42))- или они оба даты и имеют то же самое
valueOf()- или они оба одного типа и не null и...
- они не являются объектами и равны в
==(ловит числа / строки / булевы)- или, игнорируя свойства с
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; } }
Я написал этот кусок кода для сравнения объектов, и это, кажется, работает. проверьте утверждения:
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;}));
Я немного изменил код выше. для меня 0 != = false и null != = неопределенный. Если вам не нужна такая строгая проверка, удалите один "=" знак "Это[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};а==б ожидал, правда; возвращаем true
а==с ожидаемым ложные; возвращает false
C==d ожидаемое значение false; возвращается ложь
A==e ожидается false; возвращается false
f==g ожидается true; возвращается true
h==g ожидается false; возвращается false
i==j ожидается true; возвращается true
д==к ожидаемым ложные; возвращает false
k==l ожидается false; возвращается false
Если вы хотите проверить методы явно, вы можете использовать этот метод.toSource() или метод.метод toString.
вот моя версия, в значительной степени материал из этого потока интегрирован (то же самое относится к тестовым случаям):
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, возможно, это поможет вам:
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
Comments