Come determinare l'uguaglianza per due oggetti JavaScript?


670

Un rigoroso operatore di uguaglianza ti dirà se due tipi di oggetti sono uguali. Tuttavia, c'è un modo per dire se due oggetti sono uguali, proprio come il valore del codice hash in Java?

Stack Overflow question Esiste qualche tipo di funzione hashCode in JavaScript? è simile a questa domanda, ma richiede una risposta più accademica. Lo scenario sopra mostra perché sarebbe necessario averne uno e mi chiedo se esiste una soluzione equivalente .


3
Guarda anche questa domanda stackoverflow.com/q/1068834/1671639
Praveen

37
Si noti che, anche in Java, a.hashCode() == b.hashCode()non non implica che aè uguale a b. È una condizione necessaria, non sufficiente.
Heinzi,


2
Se DEVI confrontare oggetti nel tuo codice, probabilmente stai scrivendo il codice in modo errato. La domanda migliore potrebbe essere: "Come posso scrivere questo codice in modo da non dover confrontare oggetti?"
th317erd

3
@ th317erd puoi per favore spiegarti? ...
El Mac

Risposte:


175

La breve risposta

La semplice risposta è: No, non esistono mezzi generici per determinare se un oggetto è uguale a un altro nel senso che intendi. L'eccezione è quando stai pensando rigorosamente a un oggetto che non è tipizzato.

La lunga risposta

Il concetto è quello di un metodo Equals che confronta due diverse istanze di un oggetto per indicare se sono uguali a livello di valore. Tuttavia, spetta al tipo specifico definire come Equalsdeve essere implementato un metodo. Un confronto iterativo di attributi che hanno valori primitivi potrebbe non essere sufficiente, potrebbero esserci degli attributi che non devono essere considerati parte del valore dell'oggetto. Per esempio,

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

In questo caso, cnon è davvero importante determinare se due istanze di MyClass sono uguali, solo ae bimportanti. In alcuni casi cpotrebbe variare tra istanze e tuttavia non essere significativo durante il confronto.

Si noti che questo problema si applica quando i membri possono essere essi stessi esempi di un tipo e ognuno di essi dovrebbe avere tutti i mezzi per determinare l'uguaglianza.

Un'ulteriore complicazione è che in JavaScript la distinzione tra dati e metodo è sfocata.

Un oggetto può fare riferimento a un metodo che deve essere chiamato come gestore di eventi e questo probabilmente non sarebbe considerato parte del suo "stato del valore". Considerando che a un altro oggetto può essere assegnata una funzione che esegue un calcolo importante e quindi rende questa istanza diversa dalle altre semplicemente perché fa riferimento a una funzione diversa.

Che dire di un oggetto che ha uno dei suoi metodi prototipo esistenti sovrascritto da un'altra funzione? Potrebbe ancora essere considerato uguale a un'altra istanza che altrimenti identico? È possibile rispondere a questa domanda solo in ciascun caso specifico per ciascun tipo.

Come affermato in precedenza, l'eccezione sarebbe un oggetto rigorosamente non tipizzato. Nel qual caso l'unica scelta sensata è un confronto iterativo e ricorsivo di ciascun membro. Anche allora bisogna chiedersi qual è il "valore" di una funzione?


176
Se stai usando la sottolineatura, puoi semplicemente farlo_.isEqual(obj1, obj2);
chovy

12
@Harsh, la risposta non è riuscita a dare alcuna soluzione perché non ce n'è. Anche in Java, non esiste un proiettile d'argento per obiettare il confronto sull'uguaglianza e implementare correttamente il .equalsmetodo non è banale, motivo per cui esiste un argomento così dedicato in Effective Java .
LC

3
@Kumar Harsh, Ciò che rende uguali due oggetti è molto specifico dell'applicazione; non tutte le proprietà di un oggetto devono essere necessariamente prese in considerazione, quindi forzare brutalmente ogni proprietà di un oggetto non è una soluzione concreta.
sethro,

Googled javascript equality object, TL ottenuto; risposta dr, prese uno-liner dal commentare @chovy. grazie
Andrea,

Se si utilizza angolare, si haangular.equals
boatcoder

508

Perché reinventare la ruota? Dare Lodash una prova. Ha un numero di funzioni indispensabili come isEqual () .

_.isEqual(object, other);

Controllerà forzatamente ogni valore chiave - proprio come gli altri esempi in questa pagina - usando ECMAScript 5 e ottimizzazioni native se sono disponibili nel browser.

Nota: in precedenza questa risposta raccomandava Underscore.js , ma lodash ha svolto un lavoro migliore nel correggere i bug e nell'affrontare i problemi con coerenza.


27
La funzione isEqual di Underscore è molto bella (ma devi estrarre la loro libreria per usarla - circa 3K compressi).
mckoss,

29
se guardi cos'altro ti dà la sottolineatura, non ti pentirai di averlo
inserito

6
Anche se non puoi permetterti di avere un trattino basso come dipendenza, estrai la funzione isEqual, soddisfa i requisiti di licenza e vai avanti. È di gran lunga il test di uguaglianza più completo menzionato su StackOverflow.
Dale Anderson,

7
C'è un fork di Underscore chiamato LoDash e quell'autore è molto interessato a problemi di coerenza come quello. Prova con LoDash e vedi cosa ottieni.
CoolAJ86,

6
@mckoss puoi usare il modulo standalone se non vuoi l'intera libreria npmjs.com/package/lodash.isequal
Rob Fox

161

L'operatore di uguaglianza predefinito in JavaScript per gli oggetti restituisce true quando si riferiscono alla stessa posizione in memoria.

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

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

Se hai bisogno di un operatore di uguaglianza diverso, dovrai aggiungere un equals(other)metodo o qualcosa di simile alle tue classi e le specifiche del tuo dominio problematico determineranno esattamente cosa significa.

Ecco un esempio di carta da gioco:

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

Se gli oggetti possono essere convertiti in una stringa JSON, allora rende semplice una funzione equals ().
scatta il

3
@scotts Non sempre. La conversione di oggetti in JSON e il confronto di stringhe può diventare intensivo dal punto di vista computazionale per oggetti complessi in cicli stretti. Per oggetti semplici probabilmente non importa molto, ma in realtà dipende davvero dalla tua situazione specifica. Una soluzione corretta può essere semplice come confrontare gli ID oggetto o controllare ogni proprietà, ma la sua correttezza è dettata interamente dal dominio problematico.
Daniel X Moore,

Non dovremmo confrontare anche il tipo di dati ?! return other.rank === this.rank && other.suit === this.suit;
Devsathish,

1
@devsathish probabilmente no. In JavaScript i tipi sono piuttosto veloci e sciolti, ma se nel tuo dominio i tipi sono importanti, potresti voler controllare anche i tipi.
Daniel X Moore,

7
@scotts Un altro problema con la conversione in JSON è che l'ordine delle proprietà nella stringa diventa significativo. {x:1, y:2}! =={y:2, x:1}
Stijn de Witt

81

Se lavori in AngularJS , la angular.equalsfunzione determinerà se due oggetti sono uguali. In Ember.js utilizzare isEqual.

  • angular.equals- Vedi i documenti o la fonte per ulteriori informazioni su questo metodo. Fa un confronto profondo anche sugli array.
  • Ember.js isEqual: consulta la documentazione o l' origine per ulteriori informazioni su questo metodo. Non esegue un confronto approfondito sugli array.

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>


66

Questa è la mia versione Sta usando la nuova funzione Object.keys che viene introdotta in ES5 e idee / test da + , + 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 } }));


objectEquals([1,2,undefined],[1,2])ritornatrue
Roy Tinker,

objectEquals([1,2,3],{0:1,1:2,2:3})ritorna anche true- ad es. non c'è controllo del tipo, solo controllo chiave / valore.
Roy Tinker,

objectEquals(new Date(1234),1234)ritornatrue
Roy Tinker,

1
if (x.constructor! == y.constructor) {return false; } Ciò si interromperebbe se si confrontano due 'nuove stringhe (' a ')' in finestre diverse. Per l'uguaglianza di valore, dovresti verificare se String.isString su entrambi gli oggetti, quindi utilizzare un controllo di uguaglianza sciolto 'a == b'.
Triynko,

1
C'è un'enorme differenza tra l'uguaglianza "di valore" e l'uguaglianza "rigorosa" e non dovrebbero essere implementate allo stesso modo. L'eguaglianza di valore non dovrebbe interessarsi dei tipi, a parte la struttura di base, che è una di queste 4: 'oggetto' (cioè una raccolta di coppie chiave / valore), 'numero', 'stringa' o 'matrice'. Questo è tutto. Qualunque cosa che non sia un numero, una stringa o un array, deve essere confrontata come un insieme di coppie chiave / valore, indipendentemente da cosa sia il costruttore (cross-window-safe). Quando si confrontano oggetti, equiparare il valore dei numeri letterali e delle istanze di Numero, ma non forzare le stringhe in numeri.
Triynko,

50

Se si utilizza una libreria JSON, è possibile codificare ciascun oggetto come JSON, quindi confrontare le stringhe risultanti per l'uguaglianza.

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

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

NOTA: Sebbene questa risposta funzionerà in molti casi, come indicato da molte persone nei commenti, è problematico per una serie di motivi. In quasi tutti i casi ti consigliamo di trovare una soluzione più solida.


91
Interessante, ma un po 'complicato secondo me. Ad esempio, puoi garantire al 100% che le proprietà dell'oggetto vengano generate sempre nello stesso ordine?
Guido,

25
Questa è una buona domanda, e ne solleva un'altra, se due oggetti con le stesse proprietà in ordini diversi siano davvero uguali o meno. Dipende da cosa intendi per pari, immagino.
Joel Anair,

11
Si noti che la maggior parte dei codificatori e stringificatori ignorano le funzioni e convertono i numeri non infiniti, come NaN, in null.
Stephen Belanger,

4
Concordo con Guido, l'ordine delle proprietà è importante e non può essere garantito. @JoelAnair, penso che due oggetti con le stesse proprietà in ordini diversi dovrebbero essere considerati uguali se il valore delle proprietà è uguale.
Juzer Ali,

5
Questo potrebbe funzionare con un stringizer JSON alternativo, uno che ordina le chiavi degli oggetti in modo coerente.
Roy Tinker,

40

Breve deepEqualimplementazione funzionale :

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

Modifica : versione 2, usando il suggerimento del braccio e le funzioni della freccia 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);
}

5
È possibile sostituire reducecon everyper semplificare.
fiocco

1
@nonkertompf sicuro di poter: Object.keys(x).every(key => deepEqual(x[key], y[key])).
fiocco

2
Ciò fallisce quando si confrontano due date
Greg

3
deepEqual ({}, []) restituisce true
AlexMorley-Finch il

3
sì, se ti interessa un caso del genere, la brutta soluzione è sostituirla : (x === y)con: (x === y && (x != null && y != null || x.constructor === y.constructor))
atmin

22

Stai cercando di verificare se due oggetti sono uguali? cioè: le loro proprietà sono uguali?

In questo caso, probabilmente avrai notato questa situazione:

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

potresti dover fare qualcosa del genere:

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

Ovviamente quella funzione potrebbe fare con un bel po 'di ottimizzazione e la possibilità di fare un controllo approfondito (per gestire oggetti nidificati:) var a = { foo : { fu : "bar" } }ma hai l'idea.

Come sottolineato da FOR, potrebbe essere necessario adattarlo per i propri scopi, ad esempio: classi diverse possono avere definizioni diverse di "uguale". Se stai solo lavorando con oggetti semplici, quanto sopra potrebbe essere sufficiente, altrimenti una MyClass.equals()funzione personalizzata potrebbe essere la strada da percorrere.


È un metodo lungo ma verifica completamente gli oggetti senza fare ipotesi sull'ordine delle proprietà in ciascun oggetto.
briancollins081

22

Se hai una funzione di copia profonda a portata di mano, puoi usare il seguente trucco per continuare a usare JSON.stringifymentre abbini l'ordine delle proprietà:

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

Demo: http://jsfiddle.net/CU3vb/3/

Fondamento logico:

Dal momento che le proprietà di obj1 vengono copiate sul clone una per una, il loro ordine nel clone verrà preservato. E quando le proprietà di obj2vengono copiate nel clone, poiché le proprietà già esistenti obj1verranno semplicemente sovrascritte, i loro ordini nel clone verranno preservati.


11
Non credo che la conservazione degli ordini sia garantita su browser / motori.
Jo Liss

Citazioni @JoLiss necessarie;) Ricordo di averlo provato su più browser, ottenendo risultati coerenti. Ma, naturalmente, nessuno può garantire che il comportamento rimanga lo stesso nei browser / motori futuri. Questo è un trucco (come già detto nella risposta) al massimo, e non intendevo che fosse un modo infallibile per confrontare gli oggetti.
Ates Goral,

1
Certo, ecco alcuni suggerimenti: la specifica ECMAScript dice che l'oggetto è "non ordinato" ; e questa risposta per l'effettivo comportamento divergente sui browser attuali.
Jo Liss

2
@JoLiss Grazie per quello! Ma tieni presente che non ho mai rivendicato il mantenimento dell'ordine tra il codice e l'oggetto compilato. Stavo reclamando il mantenimento dell'ordine delle proprietà i cui valori vengono sostituiti sul posto. Questa era la chiave della mia soluzione: usare un mixin per sovrascrivere i valori delle proprietà. Supponendo che le implementazioni generalmente scelgano di utilizzare una sorta di hashmap, la sostituzione dei soli valori dovrebbe preservare l'ordine delle chiavi. È proprio questo che avevo testato su diversi browser.
Ates Goral

1
@AtesGoral: è possibile rendere questo vincolo un po 'più esplicito (grassetto, ...). Molte persone fanno semplicemente copia-incolla senza leggere il testo intorno ad esso ...
Willem Van Onsem,

21

In Node.js, puoi usare il suo nativo require("assert").deepStrictEqual. Ulteriori informazioni: http://nodejs.org/api/assert.html

Per esempio:

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

Un altro esempio che restituisce true/ falseinvece di restituire errori:

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

Chaiha anche questa funzione. Nel suo caso, utilizzerai:var foo = { a: 1 }; var bar = { a: 1 }; expect(foo).to.deep.equal(bar); // true;
Folusho Oladipo

Alcune versioni di Node.js sono impostate error.namesu "AssertionError [ERR_ASSERTION]". In questo caso, sostituirei l'istruzione if con if (error.code === 'ERR_ASSERTION') {.
Knute Knudsen,

Non avevo idea che deepStrictEqualfosse la strada da percorrere. Mi ero distrutto il cervello cercando di capire perché strictEqualnon funzionasse. Fantastico.
NetOperator Wibby,

19

Soluzioni logiche e semplici per confrontare qualsiasi cosa come Object, Array, String, Int ...

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

Nota:

  • devi sostituire val1e val2con il tuo oggetto
  • per l'oggetto, è necessario ordinare (per chiave) in modo ricorsivo per entrambi gli oggetti laterali

6
Suppongo che questo non funzionerà in molti casi perché l'ordine delle chiavi negli oggetti non ha importanza, a meno che JSON.stringifynon sia un riordino alfabetico? (Che non riesco a trovare documentato .)
Bram Vanroy il

sì hai ragione ... per l'oggetto, devi ordinare in modo ricorsivo per entrambi gli oggetti laterali
Pratik Bhalodiya,

2
Questo non funziona per gli oggetti con riferimenti circolari
Nate-Bit Int

13

Uso questa comparablefunzione per produrre copie dei miei oggetti paragonabili 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>

È utile nei test (la maggior parte dei framework di test ha una isfunzione). Per esempio

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

Se viene rilevata una differenza, le stringhe vengono registrate, rendendo le differenze intercambiabili:

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

1
buona idea (nel mio caso gli oggetti da confrontare sono solo coppie chiave / valore, niente cose speciali)
mech

10

Ecco una soluzione in ES6 / ES2015 usando un approccio di tipo funzionale:

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 disponibile qui


Non funziona se l'ordine delle chiavi dell'oggetto è cambiato.
Isaac Pak

9

Non so se qualcuno abbia pubblicato qualcosa di simile a questo, ma ecco una funzione che ho fatto per verificare la parità degli oggetti.

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

Inoltre, è ricorsivo, quindi può anche verificare la profonda uguaglianza, se è così che lo chiami.


piccola correzione: prima di passare attraverso ciascun oggetto di scena in aeb aggiungere questo segno di spunta se (Object.getOwnPropertyNames (a) .length! == Object.getOwnPropertyNames (b) .length) restituisce false
Con il

1
è ovvio che il correttore di uguaglianza deve essere ricorsivo. Penso che una di queste risposte ricorsive dovrebbe essere la risposta corretta. La risposta accettata non fornisce codice e non aiuta
canbax

9

Per quelli di voi che usano NodeJS, esiste un metodo conveniente chiamato isDeepStrictEqualsulla libreria Util nativa che può ottenere questo risultato.

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


le sue prestazioni dovrebbero essere buone. Nessun problema. Anche io lo uso anche in scenari complessi. Come quando lo usiamo, non dobbiamo preoccuparci se la proprietà dell'oggetto porta Object o array. Json.Stringify lo rende comunque stringa e il confronto delle stringhe in JavaScript non è un grosso problema
TrickOrTreat

6

ES6: Il codice minimo che ho potuto ottenere è questo. Fa un confronto profondo ricorsivamente stringendo tutti gli oggetti, l'unica limitazione è che nessun metodo o simbolo viene confrontato.

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


Questa è una risposta completamente funzionale, grazie @Adriano Spadoni. Sai come posso ottenere la chiave / l'attributo che è stato modificato? Grazie,
digitai

1
ciao @digital, se hai bisogno di quali tasti sono diversi, questa non è la funzione ideale. Controlla l'altra risposta e usane una con un ciclo tra gli oggetti.
Adriano Spadoni,

5

puoi usarlo _.isEqual(obj1, obj2)dalla libreria underscore.js.

Ecco un esempio:

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

Consulta la documentazione ufficiale da qui: http://underscorejs.org/#isEqual


5

Supponendo che l'ordine delle proprietà nell'oggetto non sia cambiato.

JSON.stringify () funziona per entrambi i tipi di oggetti deep e non deep, non molto sicuro degli aspetti prestazionali:

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


1
Questo non fa ciò che OP vuole, poiché corrisponderà solo se entrambi gli oggetti hanno tutte le stesse chiavi, che affermano che non lo faranno. Richiederebbe inoltre che le chiavi siano nello stesso ordine, il che non è molto ragionevole.
SpeedOfRound

1
Quali sono le proprietà sono in ordine diverso ??? No, non è un buon metodo
Vishal Sakaria,

4

Una semplice soluzione a questo problema che molte persone non capiscono è quella di ordinare le stringhe JSON (per carattere). Questo di solito è anche più veloce delle altre soluzioni menzionate qui:

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

Un'altra cosa utile di questo metodo è che puoi filtrare i confronti passando una funzione di "sostituzione" alle funzioni JSON.stringify ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON / stringify # Example_of_using_replacer_parameter ). Quanto segue confronterà solo tutte le chiavi degli oggetti che sono chiamate "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;
});

1
Oh, ho anche dimenticato, ma la funzione può essere accelerata testando prima l'oggetto che equivale e che esegue il bailing in anticipo se sono lo stesso oggetto: if (obj1 === obj2) restituisce vero;
th317erd,

11
areEqual({a: 'b'}, {b: 'a'})ottiene trueallora?
okm

Sì, mi sono reso conto dopo aver pubblicato che questa "soluzione" ha dei problemi. Per funzionare correttamente è necessario un po 'più di lavoro nell'algoritmo di ordinamento.
th317erd

4

Volevo solo contribuire con la mia versione del confronto di oggetti utilizzando alcune funzionalità es6. Non tiene conto di un ordine. Dopo aver convertito tutti gli if / else in ternary sono arrivato con il seguente:

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;

        }
    )
}

3

Avendo bisogno di una funzione di confronto degli oggetti più generica di quanto fosse stato pubblicato, ho elaborato quanto segue. Critica apprezzata ...

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

2
Ho finito per usare una variante di questo. Grazie per l'idea di contare i membri!
NateS,

2
Fai molta attenzione alla modifica Object.prototype: nella stragrande maggioranza dei casi non è consigliabile (ad esempio le integrazioni compaiono per ... in loop). Forse prendere in considerazione Object.equals = function(aObj, bObj) {...}?
Roy Tinker,

3

Se si confrontano oggetti JSON è possibile utilizzare 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 due oggetti sono diversi, {$rename:{...}, $unset:{...}, $set:{...}}viene restituito un oggetto simile a MongoDB compatibile .


3

Ho affrontato lo stesso problema e ho deciso di scrivere la mia soluzione. Ma poiché voglio anche confrontare le matrici con gli oggetti e viceversa, ho creato una soluzione generica. Ho deciso di aggiungere le funzioni al prototipo, ma è possibile riscriverle facilmente in funzioni autonome. Ecco il codice:

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

Questo algoritmo è diviso in due parti; La funzione equals stessa e una funzione per trovare l'indice numerico di una proprietà in un array / oggetto. La funzione find è necessaria solo perché indexof trova solo numeri e stringhe e nessun oggetto.

Si può chiamare così:

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

La funzione restituisce true o false, in questo caso true. L'algoritmo als consente il confronto tra oggetti molto complessi:

({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"]})

L'esempio superiore restituirà vero, anche se le proprietà hanno un ordine diverso. Un piccolo dettaglio da cercare: questo codice controlla anche lo stesso tipo di due variabili, quindi "3" non è lo stesso di 3.


3

Vedo le risposte del codice spaghetti. Senza usare librerie di terze parti, questo è molto semplice.

In primo luogo ordinare i due oggetti in base alla chiave dei nomi delle chiavi.

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)

Quindi usa semplicemente una stringa per confrontarli.

JSON.stringify(objectOne) === JSON.stringify(objectTwo)

Anche questo non funziona per la copia profonda, ha solo una profondità di iterazione.
andras

Penso che @andras significhi che devi ordinare in modo ricorsivo le chiavi degli oggetti nidificati.
Davi Lima,

2

Vorrei sconsigliare l'hashing o la serializzazione (come suggerisce la soluzione JSON). Se è necessario verificare se due oggetti sono uguali, è necessario definire cosa significa uguale. È possibile che tutti i membri dei dati in entrambi gli oggetti corrispondano o che le posizioni della memoria debbano corrispondere (nel senso che entrambe le variabili fanno riferimento allo stesso oggetto in memoria) oppure che deve corrispondere solo un membro dei dati in ciascun oggetto.

Di recente ho sviluppato un oggetto il cui costruttore crea un nuovo id (a partire da 1 e incrementando di 1) ogni volta che viene creata un'istanza. Questo oggetto ha una funzione isEqual che confronta quel valore id con il valore id di un altro oggetto e restituisce true se corrispondono.

In quel caso ho definito "uguale" nel senso che i valori dell'id corrispondono. Dato che ogni istanza ha un ID univoco, questo potrebbe essere usato per imporre l'idea che gli oggetti corrispondenti occupino anche la stessa posizione di memoria. Anche se non è necessario.


2

È utile considerare due oggetti uguali se hanno tutti gli stessi valori per tutte le proprietà e ricorsivamente per tutti gli oggetti e le matrici nidificati. Considero uguali i seguenti due oggetti:

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

Allo stesso modo, le matrici possono avere elementi "mancanti" ed elementi non definiti. Tratterei anche quelli allo stesso modo:

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

Una funzione che implementa questa definizione di uguaglianza:

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

Codice sorgente (comprese le funzioni di supporto, generalType e uniqueArray): qui Test unità e Test Runner .


2

Sto facendo i seguenti presupposti con questa funzione:

  1. Controlli gli oggetti che stai confrontando e hai solo valori primitivi (es. Oggetti non nidificati, funzioni, ecc.).
  2. Il tuo browser ha il supporto per Object.keys .

Questo dovrebbe essere trattato come una dimostrazione di una strategia semplice.

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

2

Questa è un'aggiunta per tutto quanto sopra, non una sostituzione. Se è necessario eseguire il confronto veloce di oggetti poco profondi senza dover controllare casi ricorsivi aggiuntivi. Ecco uno scatto.

Questo confronta per: 1) Uguaglianza del numero di proprietà proprie, 2) Uguaglianza dei nomi delle chiavi, 3) se bCompareValues ​​== true, Uguaglianza dei valori delle proprietà corrispondenti e loro tipi (tripla uguaglianza)

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

2

Per confrontare le chiavi per semplici istanze di oggetti coppia chiave / valore, utilizzo:

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

Una volta confrontati i tasti, for..inè sufficiente un semplice ciclo aggiuntivo .

La complessità è O (N * N) con N è il numero di chiavi.

Spero / suppongo che gli oggetti che definisco non conterranno più di 1000 proprietà ...


2

So che è un po 'vecchio, ma vorrei aggiungere una soluzione che mi è venuta in mente per questo problema. Avevo un oggetto e volevo sapere quando i suoi dati sono cambiati. "qualcosa di simile a Object.observe" e quello che ho fatto è stato:

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

Questo qui può essere duplicato e creare un altro set di matrici per confrontare i valori e le chiavi. È molto semplice perché ora sono array e restituiranno false se gli oggetti hanno dimensioni diverse.


1
Ciò restituirà sempre false, poiché le matrici non vengono confrontate per valore, ad es [1,2] != [1,2].
fiocco
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.