Como determinar a igualdade para dois objetos JavaScript?

670

Um operador de igualdade estrita dirá se dois tipos de objetos são iguais. No entanto, existe uma maneira de saber se dois objetos são iguais, assim como o valor do código hash em Java?

Pergunta sobre estouro de pilha Existe algum tipo de função hashCode no JavaScript? é semelhante a esta pergunta, mas requer uma resposta mais acadêmica. O cenário acima demonstra por que seria necessário ter um, e eu estou querendo saber se existe alguma solução equivalente .

Comunidade
fonte
3
Veja também esta questão stackoverflow.com/q/1068834/1671639
Praveen 6/14
37
Note-se que, mesmo em Java, a.hashCode() == b.hashCode()que não implica que aé igual b. É uma condição necessária, não suficiente.
Heinzi 24/09
Pls ter um olhar para este qs: stackoverflow.com/questions/35933616/...
forgottofly
2
Se você precisa comparar objetos no seu código, provavelmente está escrevendo o código errado. A melhor pergunta pode ser: "Como posso escrever esse código para não precisar comparar objetos?"
Th317erd
3
@ th317erd você pode explicar-se ...?
El Mac

Respostas:

175

A resposta curta

A resposta simples é: Não, não há meios genéricos para determinar que um objeto é igual a outro no sentido que você quer dizer. A exceção é quando você está pensando estritamente em um objeto que não possui tipo de letra.

A resposta longa

O conceito é o de um método Equals que compara duas instâncias diferentes de um objeto para indicar se elas são iguais em um nível de valor. No entanto, cabe ao tipo específico definir como um Equalsmétodo deve ser implementado. Uma comparação iterativa de atributos que possuem valores primitivos pode não ser suficiente, pode haver atributos que não devem ser considerados parte do valor do objeto. Por exemplo,

 function MyClass(a, b)
 {
     var c;
     this.getCLazy = function() {
         if (c === undefined) c = a * b // imagine * is really expensive
         return c;
     }
  }

Nesse caso acima, cnão é realmente importante determinar se duas instâncias do MyClass são iguais, somente ae bimportantes. Em alguns casos, cpode variar entre instâncias e ainda não ser significativo durante a comparação.

Observe que esse problema se aplica quando os membros também podem ser instâncias de um tipo e cada um deles deveria ter um meio de determinar a igualdade.

O que complica ainda mais é que no JavaScript a distinção entre dados e método é embaçada.

Um objeto pode fazer referência a um método que deve ser chamado como manipulador de eventos, e isso provavelmente não seria considerado parte do seu 'estado do valor'. Enquanto outro objeto pode receber uma função que executa um cálculo importante e, assim, torna essa instância diferente das outras simplesmente porque faz referência a uma função diferente.

E um objeto que possui um dos métodos de protótipo existentes substituído por outra função? Ainda poderia ser considerado igual a outra instância que, de outra forma, seria idêntica? Essa pergunta só pode ser respondida em cada caso específico de cada tipo.

Como afirmado anteriormente, a exceção seria um objeto estritamente sem tipo. Nesse caso, a única escolha sensata é uma comparação iterativa e recursiva de cada membro. Mesmo assim, é preciso perguntar qual é o "valor" de uma função?

AnthonyWJones
fonte
176
Se você estiver usando sublinhado, você pode simplesmente fazer_.isEqual(obj1, obj2);
chovy
12
@ Dura, a resposta falhou em dar qualquer solução, porque não há nenhuma. Mesmo em Java, não há um marcador de prata para comparar a igualdade de objetos e implementar corretamente o .equalsmétodo não é trivial, e é por isso que existe um tópico dedicado ao Java Efetivo .
Lcn
3
@Kumar Harsh, O que torna dois objetos iguais é muito específico para a aplicação; nem todas as propriedades de um objeto devem necessariamente ser levadas em consideração; portanto, forçar brutalmente todas as propriedades de um objeto também não é uma solução concreta.
Sethro
pesquisei no Google javascript equality object, recebi a resposta do dr., peguei uma frase do comentário @chovy. obrigado
Andrea
Se usando angular, você temangular.equals
boatcoder
508

Por que reinventar a roda? Experimente o Lodash . Ele possui várias funções obrigatórias , como isEqual () .

_.isEqual(object, other);

A força bruta verificará cada valor de chave - assim como os outros exemplos nesta página - usando o ECMAScript 5 e otimizações nativas, se estiverem disponíveis no navegador.

Nota: Anteriormente, esta resposta recomendava o Underscore.js , mas o lodash fez um trabalho melhor ao corrigir bugs e solucionar problemas com consistência.

CoolAJ86
fonte
27
A função isEqual do Underscore é muito boa (mas você precisa puxar a biblioteca deles para usá-la - cerca de 3K compactados).
Mckoss 28/08
29
se você olhar para o que mais sublinhado dá-lhe, você não vai se arrepender puxando-a
PandaWood
6
Mesmo que você não possa ter sublinhado como dependência, retire a função isEqual, atenda aos requisitos de licença e siga em frente. É de longe o teste de igualdade mais abrangente mencionado no stackoverflow.
Dale Anderson
7
Há um garfo do Underscore chamado LoDash e esse autor está muito preocupado com questões de consistência como essa. Teste com LoDash e veja o que você recebe.
12263 CoolAJ86
6
@mckoss você pode usar o módulo autônomo se você não quer que a toda a biblioteca npmjs.com/package/lodash.isequal
Rob Fox
161

O operador de igualdade padrão no JavaScript for Objects produz true quando se refere ao mesmo local na memória.

var x = {};
var y = {};
var z = x;

x === y; // => false
x === z; // => true

Se você precisar de um operador de igualdade diferente, precisará adicionar um equals(other)método ou algo parecido às suas classes e as especificidades do domínio do problema determinarão exatamente o que isso significa.

Aqui está um exemplo de baralho:

function Card(rank, suit) {
  this.rank = rank;
  this.suit = suit;
  this.equals = function(other) {
     return other.rank == this.rank && other.suit == this.suit;
  };
}

var queenOfClubs = new Card(12, "C");
var kingOfSpades = new Card(13, "S");

queenOfClubs.equals(kingOfSpades); // => false
kingOfSpades.equals(new Card(13, "S")); // => true
Daniel X Moore
fonte
Se o (s) objeto (s) puderem ser convertidos em uma sequência JSON, simplificará a função equals ().
12-13
3
@scotts Nem sempre. A conversão de objetos em JSON e a comparação de cadeias de caracteres pode se tornar computacionalmente intensiva para objetos complexos em loops apertados. Para objetos simples, provavelmente não importa muito, mas, na realidade, depende realmente da sua situação específica. Uma solução correta pode ser tão simples quanto comparar IDs de objetos ou verificar cada propriedade, mas sua correção é ditada inteiramente pelo domínio do problema.
Daniel X Moore
Não devemos comparar o tipo de dados também ?! retornar other.rank === this.rank && other.suit === this.suit;
Devsathish #
1
@ devsathish provavelmente não. No JavaScript, os tipos são bastante rápidos e soltos, mas se os tipos de domínio forem importantes, convém verificar os tipos também.
Daniel X Moore
7
@scotts Outro problema com a conversão para JSON é que a ordem das propriedades na string se torna significativa. {x:1, y:2}! =={y:2, x:1}
Stijn de Witt
81

Se você estiver trabalhando no AngularJS , a angular.equalsfunção determinará se dois objetos são iguais. No Ember.js, use isEqual.

  • angular.equals- Consulte os documentos ou a fonte para obter mais informações sobre este método. Também faz uma comparação profunda em matrizes.
  • Ember.js isEqual- consulte a documentação ou a fonte para obter mais informações sobre esse método. Ele não faz uma comparação profunda em matrizes.

var purple = [{"purple": "drank"}];
var drank = [{"purple": "drank"}];

if(angular.equals(purple, drank)) {
    document.write('got dat');
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>

Troy Harvey
fonte
66

Esta é a minha versão. Ele está usando o novo recurso Object.keys introduzido no ES5 e idéias / testes de + , + e + :

function objectEquals(x, y) {
    'use strict';

    if (x === null || x === undefined || y === null || y === undefined) { return x === y; }
    // after this just checking type of one would be enough
    if (x.constructor !== y.constructor) { return false; }
    // if they are functions, they should exactly refer to same one (because of closures)
    if (x instanceof Function) { return x === y; }
    // if they are regexps, they should exactly refer to same one (it is hard to better equality check on current ES)
    if (x instanceof RegExp) { return x === y; }
    if (x === y || x.valueOf() === y.valueOf()) { return true; }
    if (Array.isArray(x) && x.length !== y.length) { return false; }

    // if they are dates, they must had equal valueOf
    if (x instanceof Date) { return false; }

    // if they are strictly equal, they both need to be object at least
    if (!(x instanceof Object)) { return false; }
    if (!(y instanceof Object)) { return false; }

    // recursive object equality check
    var p = Object.keys(x);
    return Object.keys(y).every(function (i) { return p.indexOf(i) !== -1; }) &&
        p.every(function (i) { return objectEquals(x[i], y[i]); });
}


///////////////////////////////////////////////////////////////
/// The borrowed tests, run them by clicking "Run code snippet"
///////////////////////////////////////////////////////////////
var printResult = function (x) {
    if (x) { document.write('<div style="color: green;">Passed</div>'); }
    else { document.write('<div style="color: red;">Failed</div>'); }
};
var assert = { isTrue: function (x) { printResult(x); }, isFalse: function (x) { printResult(!x); } }
assert.isTrue(objectEquals(null,null));
assert.isFalse(objectEquals(null,undefined));
assert.isFalse(objectEquals(/abc/, /abc/));
assert.isFalse(objectEquals(/abc/, /123/));
var r = /abc/;
assert.isTrue(objectEquals(r, r));

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

Object.prototype.equals = function (obj) { return objectEquals(this, obj); };
var assertFalse = assert.isFalse,
    assertTrue = assert.isTrue;

assertFalse({}.equals(null));
assertFalse({}.equals(undefined));

assertTrue("hi".equals("hi"));
assertTrue(new Number(5).equals(5));
assertFalse(new Number(5).equals(10));
assertFalse(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}}));

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 i = {
    a: 'text',
    c: {
        b: [1, 0]
    }
};
var j = {
    a: 'text',
    c: {
        b: [1, 0]
    }
};
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(i.equals(j));
assertFalse(d.equals(k));
assertFalse(k.equals(l));

// from comments on stackoverflow post
assert.isFalse(objectEquals([1, 2, undefined], [1, 2]));
assert.isFalse(objectEquals([1, 2, 3], { 0: 1, 1: 2, 2: 3 }));
assert.isFalse(objectEquals(new Date(1234), 1234));

// no two different function is equal really, they capture their context variables
// so even if they have same toString(), they won't have same functionality
var func = function (x) { return true; };
var func2 = function (x) { return true; };
assert.isTrue(objectEquals(func, func));
assert.isFalse(objectEquals(func, func2));
assert.isTrue(objectEquals({ a: { b: func } }, { a: { b: func } }));
assert.isFalse(objectEquals({ a: { b: func } }, { a: { b: func2 } }));

Ebrahim Byagowi
fonte
objectEquals([1,2,undefined],[1,2])retornatrue
Roy Tinker
objectEquals([1,2,3],{0:1,1:2,2:3})também retorna true- por exemplo, não há verificação de tipo, apenas verificação de chave / valor.
Roy Tinker
objectEquals(new Date(1234),1234)retornatrue
Roy Tinker
1
if (x.constructor! == y.constructor) {retorne false; } Isso seria interrompido ao comparar duas 'novas String (' a ')' em janelas diferentes. Para igualdade de valor, você deve verificar se String.isString nos dois objetos e usar uma verificação de igualdade solta 'a == b'.
Triynko
1
Há uma enorme diferença entre igualdade de "valor" e igualdade "estrita" e elas não devem ser implementadas da mesma maneira. A igualdade de valor não deve se importar com os tipos, além da estrutura básica, que é uma dessas 4: 'objeto' (ou seja, uma coleção de pares de chave / valor), 'número', 'cadeia' ou 'matriz'. É isso aí. Qualquer coisa que não seja um número, sequência ou matriz, deve ser comparada como um conjunto de pares de chave / valor, independentemente do que o construtor é (seguro para várias janelas). Ao comparar objetos, iguale o valor de números literais e instâncias de Number, mas não force as seqüências a números.
Triynko
50

Se você estiver usando uma biblioteca JSON, poderá codificar cada objeto como JSON e comparar as cadeias resultantes para obter igualdade.

var obj1={test:"value"};
var obj2={test:"value2"};

alert(JSON.encode(obj1)===JSON.encode(obj2));

NOTA: Embora essa resposta funcione em muitos casos, como várias pessoas apontaram nos comentários, ela é problemática por vários motivos. Em praticamente todos os casos, você encontrará uma solução mais robusta.

Joel Anair
fonte
91
Interessante, mas um pouco complicado na minha opinião. Por exemplo, você pode 100% garantir que as propriedades do objeto serão geradas sempre na mesma ordem?
Guido
25
Essa é uma boa pergunta, e levanta outra, sobre se dois objetos com as mesmas propriedades em ordens diferentes são realmente iguais ou não. Depende do que você quer dizer com igual, eu acho.
Joel Anair 14/10/08
11
Observe que a maioria dos codificadores e stringifiers ignora funções e converte números não-definidos, como NaN, em nulos.
Stephen Belanger
4
Concordo com Guido, a ordem das propriedades é importante e não pode ser garantida. @ JoelAnair, acho que dois objetos com as mesmas propriedades em ordens diferentes devem ser considerados iguais se o valor das propriedades for igual.
Juzer Ali
5
Isso pode funcionar com um stringifier JSON alternativo, que classifica as chaves do objeto de forma consistente.
Roy Tinker
40

deepEqualImplementação funcional curta :

function deepEqual(x, y) {
  return (x && y && typeof x === 'object' && typeof y === 'object') ?
    (Object.keys(x).length === Object.keys(y).length) &&
      Object.keys(x).reduce(function(isEqual, key) {
        return isEqual && deepEqual(x[key], y[key]);
      }, true) : (x === y);
}

Edit : versão 2, usando a sugestão de lança e as funções de seta ES6:

function deepEqual(x, y) {
  const ok = Object.keys, tx = typeof x, ty = typeof y;
  return x && y && tx === 'object' && tx === ty ? (
    ok(x).length === ok(y).length &&
      ok(x).every(key => deepEqual(x[key], y[key]))
  ) : (x === y);
}
atmin
fonte
5
Você pode substituir reducepor everypara simplificar.
lança
1
@nonkertompf certeza de que poderia: Object.keys(x).every(key => deepEqual(x[key], y[key])).
lança
2
Isso falha quando você está comparando duas datas
Greg
3
deepEqual ({}, []) retorna verdadeiro #
AlexMorley-Finch
3
sim, se você cuidar de caso esquina, solução feio é substituir : (x === y)com: (x === y && (x != null && y != null || x.constructor === y.constructor))
atmin
22

Você está tentando testar se dois objetos são iguais? ou seja: suas propriedades são iguais?

Se for esse o caso, você provavelmente já notou esta situação:

var a = { foo : "bar" };
var b = { foo : "bar" };
alert (a == b ? "Equal" : "Not equal");
// "Not equal"

você pode ter que fazer algo assim:

function objectEquals(obj1, obj2) {
    for (var i in obj1) {
        if (obj1.hasOwnProperty(i)) {
            if (!obj2.hasOwnProperty(i)) return false;
            if (obj1[i] != obj2[i]) return false;
        }
    }
    for (var i in obj2) {
        if (obj2.hasOwnProperty(i)) {
            if (!obj1.hasOwnProperty(i)) return false;
            if (obj1[i] != obj2[i]) return false;
        }
    }
    return true;
}

Obviamente, essa função poderia ter um pouco de otimização e a capacidade de fazer uma verificação profunda (para manipular objetos aninhados :), var a = { foo : { fu : "bar" } }mas você entendeu.

Como FOR apontou, você pode ter que adaptar isso para seus próprios propósitos, por exemplo: classes diferentes podem ter definições diferentes de "igual". Se você estiver trabalhando apenas com objetos simples, as opções acima podem ser suficientes, caso contrário, uma MyClass.equals()função personalizada pode ser o caminho a percorrer.

nickf
fonte
É um método longo, mas testa completamente os objetos sem fazer nenhuma suposição na ordem das propriedades em cada objeto.
briancollins081 6/03
22

Se você tiver uma função de cópia profunda à mão, poderá usar o seguinte truque para continuar usando JSON.stringifya ordem das propriedades:

function equals(obj1, obj2) {
    function _equals(obj1, obj2) {
        return JSON.stringify(obj1)
            === JSON.stringify($.extend(true, {}, obj1, obj2));
    }
    return _equals(obj1, obj2) && _equals(obj2, obj1);
}

Demonstração: http://jsfiddle.net/CU3vb/3/

Fundamentação da petição:

Como as propriedades de obj1são copiadas para o clone uma por uma, sua ordem no clone será preservada. E quando as propriedades de obj2são copiadas para o clone, como as propriedades já existentes obj1serão simplesmente substituídas, suas ordens no clone serão preservadas.

Ates Goral
fonte
11
Não acho que a preservação da ordem seja garantida nos navegadores / mecanismos.
Jo Liss
@JoLiss Citações necessárias;) Lembro-me de testar isso em vários navegadores, obtendo resultados consistentes. Mas é claro que ninguém pode garantir que o comportamento permaneça o mesmo em futuros navegadores / mecanismos. Este é um truque (como já mencionado na resposta), na melhor das hipóteses, e não pretendi que fosse uma maneira infalível de comparar objetos.
Ates Goral 07/03
1
Claro, aqui estão alguns indicadores: A especificação do ECMAScript diz que o objeto é "não ordenado" ; e esta resposta para o comportamento real divergente nos navegadores atuais.
Jo Liss
2
@JoLiss Obrigado por isso! Mas observe que nunca reivindiquei a preservação da ordem entre o código e o objeto compilado. Eu estava reivindicando a preservação da ordem das propriedades cujos valores são substituídos no local. Essa foi a chave da minha solução: usar um mixin para substituir apenas os valores das propriedades. Supondo que as implementações geralmente optem por usar algum tipo de hashmap, a substituição de apenas valores deve preservar a ordem das chaves. Na verdade, é exatamente isso que eu testei em diferentes navegadores.
Ates Goral 08/03/2013
1
@ AtesGoral: é possível tornar essa restrição um pouco mais explícita (negrito, ...). A maioria das pessoas simplesmente fazer copy-paste sem ler o texto em torno dele ...
Willem Van Onsem
21

No Node.js, você pode usar seu nativo require("assert").deepStrictEqual. Mais informações: http://nodejs.org/api/assert.html

Por exemplo:

var assert = require("assert");
assert.deepStrictEqual({a:1, b:2}, {a:1, b:3}); // will throw AssertionError

Outro exemplo que retorna true/ em falsevez de retornar erros:

var assert = require("assert");

function deepEqual(a, b) {
    try {
      assert.deepEqual(a, b);
    } catch (error) {
      if (error.name === "AssertionError") {
        return false;
      }
      throw error;
    }
    return true;
};
Rafael Xavier
fonte
Chaitem esse recurso também. Nesse caso, você usará:var foo = { a: 1 }; var bar = { a: 1 }; expect(foo).to.deep.equal(bar); // true;
Folusho Oladipo 1/17/17
Algumas versões do Node.js estão definidas error.namecomo "AssertionError [ERR_ASSERTION]". Nesse caso, eu substituiria a instrução if por if (error.code === 'ERR_ASSERTION') {.
Knute Knudsen
Eu não tinha ideia deepStrictEqualdo caminho a seguir. Eu estava destruindo meu cérebro tentando descobrir por que strictEqualnão estava funcionando. Fantástico.
NetOperator Wibby
19

Soluções mais simples e lógicas para comparar tudo como objeto, matriz, string, int ...

JSON.stringify({a: val1}) === JSON.stringify({a: val2})

Nota:

  • você precisa substituir val1e val2com seu objeto
  • para o objeto, você deve classificar (por chave) recursivamente os dois objetos laterais
Pratik Bhalodiya
fonte
6
Estou assumindo que isso não funcionará em muitos casos porque a ordem das chaves nos objetos não importa - a menos JSON.stringifyque uma reordenação alfabética? (Que eu não posso encontrar documentado .)
Bram Vanroy
Sim, você está certo ... para o objeto, você deve ordenar recursivamente os dois objetos laterais #
Pratik Bhalodiya
2
Isso não funciona para objetos com referências circulares
Nate-Bit Int
13

Eu uso essa comparablefunção para produzir cópias dos meus objetos que são comparáveis ​​a JSON:

var comparable = o => (typeof o != 'object' || !o)? o :
  Object.keys(o).sort().reduce((c, key) => (c[key] = comparable(o[key]), c), {});

// Demo:

var a = { a: 1, c: 4, b: [2, 3], d: { e: '5', f: null } };
var b = { b: [2, 3], c: 4, d: { f: null, e: '5' }, a: 1 };

console.log(JSON.stringify(comparable(a)));
console.log(JSON.stringify(comparable(b)));
console.log(JSON.stringify(comparable(a)) == JSON.stringify(comparable(b)));
<div id="div"></div>

É útil em testes (a maioria das estruturas de teste tem uma isfunção). Por exemplo

is(JSON.stringify(comparable(x)), JSON.stringify(comparable(y)), 'x must match y');

Se uma diferença for detectada, as strings serão registradas, tornando as diferenças detectáveis:

x must match y
got      {"a":1,"b":{"0":2,"1":3},"c":7,"d":{"e":"5","f":null}},
expected {"a":1,"b":{"0":2,"1":3},"c":4,"d":{"e":"5","f":null}}.
jib
fonte
1
boa idéia (no meu caso os objetos a serem comparadas uma tecla apenas / valor pares, há coisas especiais)
mech
10

Heres é uma solução no ES6 / ES2015 usando uma abordagem de estilo funcional:

const typeOf = x => 
  ({}).toString
      .call(x)
      .match(/\[object (\w+)\]/)[1]

function areSimilar(a, b) {
  const everyKey = f => Object.keys(a).every(f)

  switch(typeOf(a)) {
    case 'Array':
      return a.length === b.length &&
        everyKey(k => areSimilar(a.sort()[k], b.sort()[k]));
    case 'Object':
      return Object.keys(a).length === Object.keys(b).length &&
        everyKey(k => areSimilar(a[k], b[k]));
    default:
      return a === b;
  }
}

demo disponível aqui

Alan R. Soares
fonte
Não funciona se a ordem das chaves do objeto foi alterada.
Isaac Pak
9

Não sei se alguém postou algo parecido com isso, mas aqui está uma função que eu fiz para verificar a igualdade de objetos.

function objectsAreEqual(a, b) {
  for (var prop in a) {
    if (a.hasOwnProperty(prop)) {
      if (b.hasOwnProperty(prop)) {
        if (typeof a[prop] === 'object') {
          if (!objectsAreEqual(a[prop], b[prop])) return false;
        } else {
          if (a[prop] !== b[prop]) return false;
        }
      } else {
        return false;
      }
    }
  }
  return true;
}

Além disso, é recursivo, para que também possa verificar a igualdade profunda, se é assim que você chama.

Zac
fonte
correção pequena: antes de passar por cada objeto em aeb adicione esta verificação se (Object.getOwnPropertyNames (a) .length! == Object.getOwnPropertyNames (b) .length) retornar false
Hith
1
é óbvio que o verificador de igualdade adequado deve ser recursivo. Eu acho que uma dessas respostas recursivas deve ser a resposta correta. A resposta aceita não fornece código e não ajuda
canbax
9

Para aqueles que usam NodeJS, existe um método conveniente chamado isDeepStrictEqualna biblioteca Util nativa que pode conseguir isso.

const util = require('util');

const obj1 = {
  foo: "bar",
  baz: [1, 2]
};

const obj2 = {
  foo: "bar",
  baz: [1, 2]
};


obj1 == obj2 // false
util.isDeepStrictEqual(obj1, obj2) // true

https://nodejs.org/api/util.html#util_util_isdeepstrictequal_val1_val2

Vaelin
fonte
seu desempenho é suposto ser bom. Não se preocupe. Mesmo eu uso isso em cenários complexos também. Como quando usamos isso, não precisamos nos preocupar se a propriedade do objeto está carregando Object ou array. JSON.stringify torna seqüência de qualquer maneira e comparação de cadeias de caracteres em javascript não é um grande negócio
TrickOrTreat
6

ES6: O código mínimo que eu poderia fazer é esse. Ele faz uma comparação profunda recursivamente, restringindo todos os objetos, a única limitação é que nenhum método ou símbolo é comparado.

const compareObjects = (a, b) => { 
  let s = (o) => Object.entries(o).sort().map(i => { 
     if(i[1] instanceof Object) i[1] = s(i[1]);
     return i 
  }) 
  return JSON.stringify(s(a)) === JSON.stringify(s(b))
}

console.log(compareObjects({b:4,a:{b:1}}, {a:{b:1},b:4}));

Adriano Spadoni
fonte
Esta é uma resposta totalmente funcional, obrigado @Adriano Spadoni. Você sabe como posso obter a chave / atributo que foi modificado? Obrigado,
digitai
1
oi @digital, se você precisar de quais teclas são diferentes, essa não é a função ideal. Verifique a outra resposta e use uma com um loop pelos objetos.
Adriano Spadoni
5

você pode usar a _.isEqual(obj1, obj2)partir da biblioteca underscore.js.

Aqui está um exemplo:

var stooge = {name: 'moe', luckyNumbers: [13, 27, 34]};
var clone  = {name: 'moe', luckyNumbers: [13, 27, 34]};
stooge == clone;
=> false
_.isEqual(stooge, clone);
=> true

Veja a documentação oficial aqui: http://underscorejs.org/#isEqual

Bentaiba Miled Basma
fonte
5

Supondo que a ordem das propriedades no objeto não seja alterada.

JSON.stringify () funciona para ambos os tipos de objetos, profundo e não profundo, sem ter certeza dos aspectos de desempenho:

var object1 = {
  key: "value"
};

var object2 = {
  key: "value"
};

var object3 = {
  key: "no value"
};

console.log('object1 and object2 are equal: ', JSON.stringify(object1) === JSON.stringify(object2));

console.log('object2 and object3 are equal: ', JSON.stringify(object2) === JSON.stringify(object3));

Mohammed Zameer
fonte
1
Isso não faz o que o OP deseja, pois só corresponderá se os dois objetos tiverem as mesmas chaves, que afirmam que não terão. Também exigiria que as chaves estivessem na mesma ordem, o que também não é realmente razoável.
SpeedOfRound
1
Quais são as propriedades estão em ordem diferente ??? Não é um bom método
Vishal Sakaria 06/03/19
4

Uma solução simples para esse problema que muitas pessoas não percebem é classificar as sequências JSON (por caractere). Isso geralmente também é mais rápido do que as outras soluções mencionadas aqui:

function areEqual(obj1, obj2) {
    var a = JSON.stringify(obj1), b = JSON.stringify(obj2);
    if (!a) a = '';
    if (!b) b = '';
    return (a.split('').sort().join('') == b.split('').sort().join(''));
}

Outra coisa útil sobre esse método é que você pode filtrar comparações passando uma função "substituta" para as funções JSON.stringify ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON / stringify # Example_of_using_replacer_parameter ). A seguir, apenas serão comparadas todas as chaves de objetos denominadas "derp":

function areEqual(obj1, obj2, filter) {
    var a = JSON.stringify(obj1, filter), b = JSON.stringify(obj2, filter);
    if (!a) a = '';
    if (!b) b = '';
    return (a.split('').sort().join('') == b.split('').sort().join(''));
}
var equal = areEqual(obj1, obj2, function(key, value) {
    return (key === 'derp') ? value : undefined;
});
th317erd
fonte
1
Ah, eu também esqueci, mas a função pode ser acelerada testando o primeiro objeto igualando e salvando cedo se eles forem o mesmo objeto: if (obj1 === obj2) retorna true;
Th317erd
11
areEqual({a: 'b'}, {b: 'a'})fica trueentão?
Ok 13/07
Sim, percebi depois de postar que esta "solução" tem problemas. Ele precisa de um pouco mais de trabalho no algoritmo de classificação para realmente funcionar corretamente.
Th317erd
4

Só queria contribuir com minha versão de comparação de objetos utilizando alguns recursos do es6. Não leva em consideração um pedido. Depois de converter todos os if / else para ternário, vim com o seguinte:

function areEqual(obj1, obj2) {

    return Object.keys(obj1).every(key => {

            return obj2.hasOwnProperty(key) ?
                typeof obj1[key] === 'object' ?
                    areEqual(obj1[key], obj2[key]) :
                obj1[key] === obj2[key] :
                false;

        }
    )
}
Egor Litvinchuk
fonte
3

Precisando de uma função de comparação de objetos mais genérica do que a postada, preparei o seguinte. Crítica apreciada ...

Object.prototype.equals = function(iObj) {
  if (this.constructor !== iObj.constructor)
    return false;
  var aMemberCount = 0;
  for (var a in this) {
    if (!this.hasOwnProperty(a))
      continue;
    if (typeof this[a] === 'object' && typeof iObj[a] === 'object' ? !this[a].equals(iObj[a]) : this[a] !== iObj[a])
      return false;
    ++aMemberCount;
  }
  for (var a in iObj)
    if (iObj.hasOwnProperty(a))
      --aMemberCount;
  return aMemberCount ? false : true;
}
Liam
fonte
2
Acabei usando uma variação disso. Obrigado pela ideia de contar membros!
NateS 26/08/10
2
Seja muito cuidadoso ao modificar Object.prototype- na grande maioria dos casos, isso não é recomendado (acréscimos aparecem em todos os loops .. nos, por exemplo). Talvez considere Object.equals = function(aObj, bObj) {...}?
Roy Tinker
3

Se você estiver comparando objetos JSON, poderá usar https://github.com/mirek/node-rus-diff

npm install rus-diff

Uso:

a = {foo:{bar:1}}
b = {foo:{bar:1}}
c = {foo:{bar:2}}

var rusDiff = require('rus-diff').rusDiff

console.log(rusDiff(a, b)) // -> false, meaning a and b are equal
console.log(rusDiff(a, c)) // -> { '$set': { 'foo.bar': 2 } }

Se dois objetos forem diferentes, um {$rename:{...}, $unset:{...}, $set:{...}}objeto semelhante compatível com o MongoDB será retornado.

Mirek Rusin
fonte
3

Enfrentei o mesmo problema e decidi escrever minha própria solução. Mas como também quero comparar matrizes com objetos e vice-versa, criei uma solução genérica. Decidi adicionar as funções ao protótipo, mas é possível reescrevê-las facilmente para funções independentes. Aqui está o código:

Array.prototype.equals = Object.prototype.equals = function(b) {
    var ar = JSON.parse(JSON.stringify(b));
    var err = false;
    for(var key in this) {
        if(this.hasOwnProperty(key)) {
            var found = ar.find(this[key]);
            if(found > -1) {
                if(Object.prototype.toString.call(ar) === "[object Object]") {
                    delete ar[Object.keys(ar)[found]];
                }
                else {
                    ar.splice(found, 1);
                }
            }
            else {
                err = true;
                break;
            }
        }
    };
    if(Object.keys(ar).length > 0 || err) {
        return false;
    }
    return true;
}

Array.prototype.find = Object.prototype.find = function(v) {
    var f = -1;
    for(var i in this) {
        if(this.hasOwnProperty(i)) {
            if(Object.prototype.toString.call(this[i]) === "[object Array]" || Object.prototype.toString.call(this[i]) === "[object Object]") {
                if(this[i].equals(v)) {
                    f = (typeof(i) == "number") ? i : Object.keys(this).indexOf(i);
                }
            }
            else if(this[i] === v) {
                f = (typeof(i) == "number") ? i : Object.keys(this).indexOf(i);
            }
        }
    }
    return f;
}

Este algoritmo é dividido em duas partes; A função igual é uma função para encontrar o índice numérico de uma propriedade em uma matriz / objeto. A função find é necessária apenas porque o indexof localiza apenas números e seqüências de caracteres e nenhum objeto.

Pode-se chamar assim:

({a: 1, b: "h"}).equals({a: 1, b: "h"});

A função retorna true ou false, nesse caso true. O algoritmo também permite a comparação entre objetos muito complexos:

({a: 1, b: "hello", c: ["w", "o", "r", "l", "d", {answer1: "should be", answer2: true}]}).equals({b: "hello", a: 1, c: ["w", "d", "o", "r", {answer1: "should be", answer2: true}, "l"]})

O exemplo superior retornará true, mesmo que as propriedades tenham uma ordem diferente. Um pequeno detalhe a ser observado: esse código também verifica o mesmo tipo de duas variáveis; portanto, "3" não é o mesmo que 3.

Sir_baaron
fonte
3

Vejo respostas do código do espaguete. Sem usar nenhuma biblioteca de terceiros, isso é muito fácil.

Primeiro, classifique os dois objetos digitando seus nomes de chave.

let objectOne = { hey, you }
let objectTwo = { you, hey }

// If you really wanted you could make this recursive for deep sort.
const sortObjectByKeyname = (objectToSort) => {
    return Object.keys(objectToSort).sort().reduce((r, k) => (r[k] = objectToSort[k], r), {});
}

let objectOne = sortObjectByKeyname(objectOne)
let objectTwo = sortObjectByKeyname(objectTwo)

Em seguida, basta usar uma string para compará-los.

JSON.stringify(objectOne) === JSON.stringify(objectTwo)
Oliver Dixon
fonte
Isso também não funciona para cópias profundas, pois possui apenas uma profundidade de iteração.
andras
Acho que @andras significa que você precisa classificar recursivamente as chaves dos objetos aninhados.
Davi Lima
2

Eu aconselho contra hash ou serialização (como a solução JSON sugere). Se você precisar testar se dois objetos são iguais, será necessário definir o que significa igual. Pode ser que todos os membros de dados em ambos os objetos correspondam, ou pode ser que os locais da memória correspondam (ou seja, ambas as variáveis ​​fazem referência ao mesmo objeto na memória) ou pode ser que apenas um membro de dados em cada objeto deve corresponder.

Recentemente, desenvolvi um objeto cujo construtor cria um novo ID (iniciando em 1 e incrementando em 1) cada vez que uma instância é criada. Este objeto possui uma função isEqual que compara esse valor de identificação com o valor de identificação de outro objeto e retorna true se corresponderem.

Nesse caso, defini "igual" como significando que os valores de id correspondem. Dado que cada instância tem um ID exclusivo, isso pode ser usado para reforçar a ideia de que objetos correspondentes também ocupam o mesmo local de memória. Embora isso não seja necessário.

Bernard Igiri
fonte
2

É útil considerar dois objetos iguais se eles tiverem os mesmos valores para todas as propriedades e recursivamente para todos os objetos e matrizes aninhados. Também considero os dois objetos a seguir iguais:

var a = {p1: 1};
var b = {p1: 1, p2: undefined};

Da mesma forma, matrizes podem ter elementos "ausentes" e elementos indefinidos. Eu trataria os mesmos também:

var c = [1, 2];
var d = [1, 2, undefined];

Uma função que implementa esta definição de igualdade:

function isEqual(a, b) {
    if (a === b) {
        return true;
    }

    if (generalType(a) != generalType(b)) {
        return false;
    }

    if (a == b) {
        return true;
    }

    if (typeof a != 'object') {
        return false;
    }

    // null != {}
    if (a instanceof Object != b instanceof Object) {
        return false;
    }

    if (a instanceof Date || b instanceof Date) {
        if (a instanceof Date != b instanceof Date ||
            a.getTime() != b.getTime()) {
            return false;
        }
    }

    var allKeys = [].concat(keys(a), keys(b));
    uniqueArray(allKeys);

    for (var i = 0; i < allKeys.length; i++) {
        var prop = allKeys[i];
        if (!isEqual(a[prop], b[prop])) {
            return false;
        }
    }
    return true;
}

Código fonte (incluindo as funções auxiliares, generalType e uniqueArray): Teste de Unidade e Teste de Execução aqui .

mckoss
fonte
2

Estou fazendo as seguintes suposições com esta função:

  1. Você controla os objetos que está comparando e só possui valores primitivos (por exemplo, objetos, funções não aninhados).
  2. Seu navegador possui suporte para Object.keys .

Isso deve ser tratado como uma demonstração de uma estratégia simples.

/**
 * Checks the equality of two objects that contain primitive values. (ie. no nested objects, functions, etc.)
 * @param {Object} object1
 * @param {Object} object2
 * @param {Boolean} [order_matters] Affects the return value of unordered objects. (ex. {a:1, b:2} and {b:2, a:1}).
 * @returns {Boolean}
 */
function isEqual( object1, object2, order_matters ) {
    var keys1 = Object.keys(object1),
        keys2 = Object.keys(object2),
        i, key;

    // Test 1: Same number of elements
    if( keys1.length != keys2.length ) {
        return false;
    }

    // If order doesn't matter isEqual({a:2, b:1}, {b:1, a:2}) should return true.
    // keys1 = Object.keys({a:2, b:1}) = ["a","b"];
    // keys2 = Object.keys({b:1, a:2}) = ["b","a"];
    // This is why we are sorting keys1 and keys2.
    if( !order_matters ) {
        keys1.sort();
        keys2.sort();
    }

    // Test 2: Same keys
    for( i = 0; i < keys1.length; i++ ) {
        if( keys1[i] != keys2[i] ) {
            return false;
        }
    }

    // Test 3: Values
    for( i = 0; i < keys1.length; i++ ) {
        key = keys1[i];
        if( object1[key] != object2[key] ) {
            return false;
        }
    }

    return true;
}
Aldo Fregoso
fonte
2

Esta é uma adição a todas as opções acima, não uma substituição. Se você precisar comparar rapidamente objetos rasos sem precisar verificar casos recursivos extras. Aqui está um tiro.

Isso se compara a: 1) Igualdade de número de propriedades próprias, 2) Igualdade de nomes de chaves, 3) se bCompareValues ​​== true, Igualdade de valores de propriedades correspondentes e seus tipos (igualdade tripla)

var shallowCompareObjects = function(o1, o2, bCompareValues) {
    var s, 
        n1 = 0,
        n2 = 0,
        b  = true;

    for (s in o1) { n1 ++; }
    for (s in o2) { 
        if (!o1.hasOwnProperty(s)) {
            b = false;
            break;
        }
        if (bCompareValues && o1[s] !== o2[s]) {
            b = false;
            break;
        }
        n2 ++;
    }
    return b && n1 == n2;
}
Lex
fonte
2

Para comparar chaves para instâncias simples de objetos pares de chave / valor, eu uso:

function compareKeys(r1, r2) {
    var nloops = 0, score = 0;
    for(k1 in r1) {
        for(k2 in r2) {
            nloops++;
            if(k1 == k2)
                score++; 
        }
    }
    return nloops == (score * score);
};

Uma vez comparadas as chaves, for..inbasta um loop adicional simples .

A complexidade é O (N * N) com N é o número de chaves.

Espero / acho que os objetos que defino não conterão mais de 1000 propriedades ...

Hefeust CORTES
fonte
2

Eu sei que isso é um pouco antigo, mas eu gostaria de adicionar uma solução que eu criei para esse problema. Eu tinha um objeto e queria saber quando seus dados foram alterados. "algo semelhante ao Object.observe" e o que eu fiz foi:

function checkObjects(obj,obj2){
   var values = [];
   var keys = [];
   keys = Object.keys(obj);
   keys.forEach(function(key){
      values.push(key);
   });
   var values2 = [];
   var keys2 = [];
   keys2 = Object.keys(obj2);
   keys2.forEach(function(key){
      values2.push(key);
   });
   return (values == values2 && keys == keys2)
}

Isso aqui pode ser duplicado e criar um outro conjunto de matrizes para comparar os valores e as chaves. É muito simples porque agora são matrizes e retornará false se os objetos tiverem tamanhos diferentes.

inoabriano
fonte
1
Isso sempre retornará false, porque matrizes não se comparam por valor, por exemplo [1,2] != [1,2].
lança