Comparando objetos em javascript

Qual é a melhor maneira de comparar objetos em javascript?

Exemplo:

 var user1 = {name : "nerd", org: "dev"}; var user2 = {name : "nerd", org: "dev"}; var eq = user1 == user2; alert(eq); // gives false 

Eu sei que dois objetos são iguais se pertencem ao mesmo objeto , mas existe uma maneira de verificar se eles têm os mesmos valores de atributo?

O seguinte método funciona para mim, mas esta é a única opção?

 var eq = Object.toJSON(user1) == Object.toJSON(user2); alert(eq); // gives true 
817
01 июля '09 в 15:18 2009-07-01 15:18 spankmaster79 está definido 01 de julho de 2009 às 15:18 2009-07-01 15:18
@ 10 respostas

Infelizmente, não existe uma maneira perfeita se você não usar _proto_ recursivamente e não acessar todas as propriedades não enumeráveis, mas isso só funciona no Firefox.

Então, posso adivinhar melhor os cenários de uso.


1) Rápido e limitado.

Ele funciona quando você tem objetos de estilo JSON simples sem métodos DOM e nós dentro:

  JSON.stringify(obj1) === JSON.stringify(obj2) 

Propriedade ORDER é importante, portanto, este método retorna false para os seguintes objetos:

  x = {a: 1, b: 2}; y = {b: 2, a: 1}; 

2) Lenta e mais geral.

Compara objetos sem invadir os protótipos, então recursivamente compara as projeções dos objetos, e também compara os construtores.

Este é quase o algoritmo certo:

 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; } 

Problemas conhecidos (bem, eles têm uma prioridade muito baixa, talvez você nunca os notará):

  • objetos com uma estrutura protótipo diferente, mas com a mesma projeção
  • funções podem ter texto idêntico, mas referem-se a diferentes portas

Testes: passa testes de como determinar a igualdade para dois objetos JavaScript? .

971
17 июля '09 в 19:08 2009-07-17 19:08 a resposta é dada pela crazyx 17 de julho de 2009 às 19:08 2009-07-17 19:08

Aqui está a minha solução comentada no ES3 (detalhes após o código):

 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; } 

Ao desenvolver esta solução, eu olhei para os casos angulares, a eficiência, mas tentando dar uma solução simples que funciona, espero, com alguma elegância. JavaScript assume que propriedades e objetos nulos e indefinidos têm protótipos que podem levar a comportamentos muito diferentes se não forem testados.

No início, decidi estender o Object em vez do Object.prototype , principalmente porque null não pode ser um dos objetos de comparação, e acredito que null deve ser um objeto válido para comparação com o outro. Existem outros problemas legais observados por outros em relação à extensão Object.prototype em relação aos possíveis efeitos colaterais de outros códigos.

Cuidado especial deve ser tomado para habilitar o JavaScript para que as propriedades do objeto possam ser definidas como indefinidas , ou seja, Existem propriedades cujos valores estão definidos como indefinidos . A solução acima confirma que os dois objetos têm as mesmas propriedades que não estão definidas para a mensagem de igualdade. Isso só pode ser feito verificando a existência de propriedades usando Object.hasOwnProperty (property_name) . Observe também que JSON.stringify () remove propriedades que são definidas como indefinidas e, portanto, comparações usando este formulário ignorarão as propriedades definidas como indefinidas .

As funções devem ser consideradas iguais apenas se usarem o mesmo link, e não apenas o mesmo código, porque não levará em consideração o protótipo dessas funções. Portanto, comparar uma linha de código não funciona para garantir que eles tenham o mesmo objeto de protótipo.

border=0

Ambos os objetos devem ter a mesma cadeia de protótipos , não as mesmas propriedades. Isso só pode ser verificado pelo comparador cruzado comparando o construtor de ambos os objetos para igualdade estrita. O ECMAScript 5 permite que você teste seu protótipo real usando Object.getPrototypeOf () . Alguns navegadores da Web também oferecem a propriedade __proto__, que faz o mesmo. Uma possível melhoria do código acima permitirá usar um desses métodos sempre que possível.

Usando comparações estritas aqui é de suma importância, porque 2 não deve ser considerado igual a "2.0000" , e falso deve ser considerado igual a zero , indefinido ou 0 .

Considerações de eficiência me levam a comparar o quanto antes a igualdade de propriedades. Então, somente se falhar, encontre o tipo dessas propriedades. O aumento na velocidade pode ser significativo para objetos grandes com um grande número de propriedades escalares.

Não são necessários mais de dois ciclos, o primeiro para verificar as propriedades do objeto à esquerda, o segundo para verificar as propriedades à direita e apenas a existência (não valores) para capturar essas propriedades que são definidas com um valor indefinido .

Em geral, esse código manipula a maioria dos casos de canto em apenas 16 linhas de código (sem comentários).

Atualização (13/08/2015) . Implementei uma versão mais eficiente, porque a função value_equals () trabalha mais rápido, processa os casos angulares corretos, como NaN e 0, diferente de -0, sem necessariamente aplicar ordem e testar as propriedades de objetos para referências circulares suportadas por mais de 100 testes automatizados como parte de Projetos de conjuntos de testes da Toubkal .

154
16 июля '11 в 1:21 2011-07-16 01:21 a resposta é dada por Jean Vincent julho 16 '11 em 1:21 2011-07-16 01:21
  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; }; 

Uma maneira simples de comparar objetos de um único nível.

22
02 мая '11 в 18:27 2011-05-02 18:27 a resposta é dada pincopallo 02 May '11 at 18:27 2011-05-02 18:27

Claro, não é a única maneira - você pode prototipar um método (contra Object aqui, mas eu certamente não sugeriria usar Object para código ativo) para replicar os métodos de comparação de estilo C # / Java.

Modifique, como esperado, um exemplo comum:

 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; } 

Note que os métodos de teste com toString () não são absolutamente bons o suficiente, mas um método que será aceitável é muito difícil devido a um problema de espaço, significativo ou não, para não mencionar sinônimos e métodos que produzem o mesmo o resultado com diferentes implementações, E os problemas de prototipagem do objeto como um todo.

20
01 июля '09 в 15:25 2009-07-01 15:25 a resposta é dada Anakata 01 de julho de 2009 às 15:25 2009-07-01 15:25

O algoritmo a seguir examinará estruturas de dados, números, cadeias de caracteres, datas e, é claro, objetos javascript aninhados simples:

Objetos são considerados equivalentes se

  • Eles são exatamente iguais em === (String e Number são primeiro descompactados para garantir que 42 equivalente a Number(42) )
  • ou são datas e têm o mesmo valueOf()
  • ou eles são do mesmo tipo, não nulos e ...
    • eles não são objetos e são iguais a == (pega números / strings / booleanos)
    • ou, ignorando propriedades com um valor de undefined , elas possuem as mesmas propriedades, todas as quais são consideradas recursivamente equivalentes.

As funções não são consideradas idênticas no texto funcional. Este teste não é suficiente porque as funções podem ter fechamentos diferentes. As funções são consideradas iguais se === diz isso (mas você pode facilmente estender essa razão equivalente se você decidir fazer isso).

Loops infinitos potencialmente causados ​​por estruturas de dados circulares são eliminados. Quando o areEquivalent tenta refutar a igualdade e recursa nas propriedades do objeto para isso, ele rastreia objetos para os quais essa comparação auxiliar é necessária. Se a igualdade puder ser refutada, algum caminho de propriedade válido será diferente entre os objetos e, em seguida, deverá haver o caminho de alcançabilidade mais curto, e o caminho de menor alcance não poderá conter os ciclos presentes nos dois caminhos; isto é, é normal aceitar igualdade quando comparamos recursivamente objetos. A suposição é armazenada na propriedade areEquivalent_Eq_91_2_34 , que é excluída após o uso, mas se o gráfico do objeto já contiver tal propriedade, o comportamento é indefinido. O uso desta propriedade de marcador é necessário porque o javascript não suporta dicionários usando objetos arbitrários como chaves.

 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; //eg 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; } } 
15
19 июня '11 в 17:29 2011-06-19 17:29 a resposta é dada por Eamon Nerbonne em 19 de junho '11 às 17:29 2011-06-19 17:29

Eu escrevi este trecho de código para comparar objetos, e parece funcionar. declarações de cheque:

 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;})); 
11
03 окт. resposta dada mhoms Oct 03 2010-10-03 14:01 '10 às 14:01 2010-10-03 14:01

Eu mudei o código um pouco mais alto. para mim, 0! == falso e nulo! == indefinido. Se você não precisa de uma verificação tão rigorosa, remova um dos ícones "=" em "this [p]! == x [p]" dentro do código.

 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; } 

Então eu verifiquei com os seguintes objetos:

 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 esperado como verdadeiro; retornou verdadeiro

a == c valor esperado é falso; falso falso

c == d valor esperado é falso; falso falso

a == e o valor esperado é falso; falso falso

f == g esperado verdadeiro; retornou verdadeiro

h == g esperado falso; falso falso

i == j esperado verdadeiro; retornou verdadeiro

d = = k esperado falso; falso falso

k == l esperado falso; falso falso

5
29 апр. A resposta é dada por Jevgeni Kiski em 29 de abril. 2010-04-29 12:17 '10 às 12:17 2010-04-29 12:17

Aqui está a minha versão, muitos materiais deste fluxo estão integrados (o mesmo se aplica aos casos de teste):

 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)); } }); 

Aqui está o meu caso de teste:

  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)); 
4
02 апр. a resposta é dada gossi 02 abr 2011-04-02 14:39 '11 às 14:39 2011-04-02 14:39

Se você quiser testar explicitamente métodos, você pode usar os métodos method.toSource () ou method.toString ().

4
01 июля '09 в 18:44 2009-07-01 18:44 a resposta é dada por Nicolas R em 01 de julho de 2009 às 18:44 2009-07-01 18:44

Se você trabalha sem uma biblioteca JSON, talvez isso ajude você a:

 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 a false 
3
09 марта '10 в 13:38 2010-03-09 13:38 a resposta é dada a Samuel Weber 09 de março de 2010 às 13:38 2010-03-09 13:38

Outras questões sobre tags ou Faça uma pergunta