Come determinare l'uguaglianza per due oggetti JavaScript?
-
03-07-2019 - |
Domanda
Un rigoroso operatore per l'uguaglianza ti dirà se due tipi di oggetto sono uguali. Tuttavia, c'è un modo per dire se due oggetti sono uguali, molto simile al valore del codice hash in Java?
Domanda Stack Overflow 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 .
Soluzione
La risposta breve
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 risposta lunga
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 deve essere implementato un metodo Equals
. 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. Ad 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, c
non è davvero importante per determinare se due istanze di MyClass sono uguali, solo a
e b
sono importante. In alcuni casi c
potrebbe variare tra istanze e tuttavia non essere significativo durante il confronto.
Nota che questo problema si applica quando i membri stessi possono essere anche istanze di un tipo e ognuno di loro dovrebbe avere tutti i mezzi per determinare l'uguaglianza.
Ulteriori complicazioni sono 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 verrebbe considerato parte del suo "stato del valore". Considerando che a un altro oggetto può essere assegnata una funzione che esegue un calcolo importante e rende quindi 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?
Altri suggerimenti
Perché reinventare la ruota? Prova Lodash . Ha una serie di funzioni indispensabili come isEqual () .
_.isEqual(object, other);
Controllerà con forza ogni valore chiave - proprio come gli altri esempi in questa pagina - usando ECMAScript & nbsp ; 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.
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 metodo uguale (altro)
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 lavori in AngularJS , la funzione angular.equals
determinerà se due oggetti sono uguali. In Ember.js usa isEqual
.
-
angular.equals
- Consulta i docs o < a href = "https://github.com/angular/angular.js/blob/6c59e770084912d2345e7f83f983092a2d305ae3/src/Angular.js#L670" rel = "noreferrer"> source per ulteriori informazioni su questo metodo. Fa un confronto profondo anche sugli array. - Ember.js
isEqual
- Vedi docs o source per ulteriori informazioni su questo metodo . Non fa un confronto profondo 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>
Questa è la mia versione. Sta usando la nuova Object.keys che è stato introdotto 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 } }));
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 diverse persone hanno sottolineato nei commenti, è problematica per una serie di motivi. In quasi tutti i casi ti consigliamo di trovare una soluzione più solida.
Implementazione funzionale deepEqual
breve:
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);
}
Se hai una funzione di copia profonda a portata di mano, puoi usare il seguente trucco per ancora usare JSON.stringify
mentre combini 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/
Motivazione:
Poiché le proprietà di obj1
vengono copiate sul clone una alla volta, il loro ordine nel clone verrà conservato. E quando le proprietà di obj2
vengono copiate nel clone, poiché le proprietà già esistenti in obj1
verranno semplicemente sovrascritte, i loro ordini nel clone verranno conservati.
Stai provando a 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 avere un bel po 'di ottimizzazione e la possibilità di fare un controllo approfondito (per gestire oggetti nidificati: var a = {foo: {fu: " bar "}}
) ma hai avuto l'idea.
Come sottolineato da FOR, potresti doverlo adattare ai tuoi 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 funzione personalizzata MyClass.equals ()
potrebbe essere la strada da percorrere.
?? più semplici e logiche per confrontare qualsiasi cosa come Object, Array, String, Int ...
JSON.stringify ({a: val1}) === JSON.stringify ({a: val2})
Nota:
- devi sostituire
val1
eval2
con il tuo oggetto - per l'oggetto, devi ordinare (per chiave) in modo ricorsivo per entrambi gli oggetti laterali
In Node.js, puoi usare il suo native (" assert "). deepEqual
. Ulteriori informazioni:
http://nodejs.org/api/assert.html
Ad esempio:
var assert = require("assert");
assert.deepEqual({a:1, b:2}, {a:1, b:3}); // will throw AssertionError
Un altro esempio che restituisce true
/ false
invece 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;
};
Uso questa funzione confrontabile
per produrre copie dei miei oggetti che sono comparabili 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 funzione is
). Per es.
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 compilabili:
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}}.
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;
}
}
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.
puoi usare _.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
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 un "rimpiazzo" funzione per le 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 denominate " 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;
});
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 di 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" come significato i valori dell'id corrispondono. Dato che ogni istanza ha un ID univoco, questo potrebbe essere usato per rafforzare l'idea che gli oggetti corrispondenti occupino anche la stessa posizione di memoria. Sebbene ciò non sia necessario.
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;
}
Se si confrontano oggetti JSON è possibile utilizzare https://github.com/mirek/node -rus-diff
npm install rus-diff
Utilizzo:
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, un compatibile con MongoDB <$> {$ rename: {...}, $ unset: {...}, $ set: {...}}
come l'oggetto è restituito.
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.
Uno può chiamarlo 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 tenere in considerazione: questo codice controlla anche lo stesso tipo di due variabili, quindi "3" non è uguale a 3.
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;
}
)
}
È 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 "manca". elementi ed elementi indefiniti. 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): Unit Test e Test Runner qui .
Con questa funzione sto formulando i seguenti presupposti:
- Controlli gli oggetti che stai confrontando e hai solo valori primitivi (ad es. oggetti non nidificati, funzioni, ecc.).
- 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;
}
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 dei 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;
}
Per confrontare le chiavi per semplici istanze di oggetti coppia chiave / valore, 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);
};
Dopo aver confrontato le chiavi, è sufficiente un semplice ciclo for..in
aggiuntivo.
La complessità è O (N * N) con N è il numero di chiavi.
Spero / suppongo che gli oggetti che definisco non contengano più di 1000 proprietà ...
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.
Estrarre dalla mia biblioteca personale, che uso ripetutamente per il mio lavoro. La seguente funzione è un lenient recursive deep equal, che non controlla
- Uguaglianza di classe
- Valori ereditati
- Valori l'uguaglianza rigorosa
Lo uso principalmente per verificare se ricevo risposte uguali contro varie implementazioni API. Dove possono verificarsi differenze di implementazione (come stringa vs numero) e valori null aggiuntivi.
La sua implementazione è piuttosto semplice e breve (se tutti i commenti vengono rimossi)
/** Recursively check if both objects are equal in value
***
*** This function is designed to use multiple methods from most probable
*** (and in most cases) valid, to the more regid and complex method.
***
*** One of the main principles behind the various check is that while
*** some of the simpler checks such as == or JSON may cause false negatives,
*** they do not cause false positives. As such they can be safely run first.
***
*** # !Important Note:
*** as this function is designed for simplified deep equal checks it is not designed
*** for the following
***
*** - Class equality, (ClassA().a = 1) maybe valid to (ClassB().b = 1)
*** - Inherited values, this actually ignores them
*** - Values being strictly equal, "1" is equal to 1 (see the basic equality check on this)
*** - Performance across all cases. This is designed for high performance on the
*** most probable cases of == / JSON equality. Consider bench testing, if you have
*** more 'complex' requirments
***
*** @param objA : First object to compare
*** @param objB : 2nd object to compare
*** @param .... : Any other objects to compare
***
*** @returns true if all equals, or false if invalid
***
*** @license Copyright by eugene@picoded.com, 2012.
*** Licensed under the MIT license: http://opensource.org/licenses/MIT
**/
function simpleRecusiveDeepEqual(objA, objB) {
// Multiple comparision check
//--------------------------------------------
var args = Array.prototype.slice.call(arguments);
if(args.length > 2) {
for(var a=1; a<args.length; ++a) {
if(!simpleRecusiveDeepEqual(args[a-1], args[a])) {
return false;
}
}
return true;
} else if(args.length < 2) {
throw "simpleRecusiveDeepEqual, requires atleast 2 arguments";
}
// basic equality check,
//--------------------------------------------
// if this succed the 2 basic values is equal,
// such as numbers and string.
//
// or its actually the same object pointer. Bam
//
// Note that if string and number strictly equal is required
// change the equality from ==, to ===
//
if(objA == objB) {
return true;
}
// If a value is a bsic type, and failed above. This fails
var basicTypes = ["boolean", "number", "string"];
if( basicTypes.indexOf(typeof objA) >= 0 || basicTypes.indexOf(typeof objB) >= 0 ) {
return false;
}
// JSON equality check,
//--------------------------------------------
// this can fail, if the JSON stringify the objects in the wrong order
// for example the following may fail, due to different string order:
//
// JSON.stringify( {a:1, b:2} ) == JSON.stringify( {b:2, a:1} )
//
if(JSON.stringify(objA) == JSON.stringify(objB)) {
return true;
}
// Array equality check
//--------------------------------------------
// This is performed prior to iteration check,
// Without this check the following would have been considered valid
//
// simpleRecusiveDeepEqual( { 0:1963 }, [1963] );
//
// Note that u may remove this segment if this is what is intended
//
if( Array.isArray(objA) ) {
//objA is array, objB is not an array
if( !Array.isArray(objB) ) {
return false;
}
} else if( Array.isArray(objB) ) {
//objA is not array, objB is an array
return false;
}
// Nested values iteration
//--------------------------------------------
// Scan and iterate all the nested values, and check for non equal values recusively
//
// Note that this does not check against null equality, remove the various "!= null"
// if this is required
var i; //reuse var to iterate
// Check objA values against objB
for (i in objA) {
//Protect against inherited properties
if(objA.hasOwnProperty(i)) {
if(objB.hasOwnProperty(i)) {
// Check if deep equal is valid
if(!simpleRecusiveDeepEqual( objA[i], objB[i] )) {
return false;
}
} else if(objA[i] != null) {
//ignore null values in objA, that objB does not have
//else fails
return false;
}
}
}
// Check if objB has additional values, that objA do not, fail if so
for (i in objB) {
if(objB.hasOwnProperty(i)) {
if(objB[i] != null && !objA.hasOwnProperty(i)) {
//ignore null values in objB, that objA does not have
//else fails
return false;
}
}
}
// End of all checks
//--------------------------------------------
// By reaching here, all iteration scans have been done.
// and should have returned false if it failed
return true;
}
// Sanity checking of simpleRecusiveDeepEqual
(function() {
if(
// Basic checks
!simpleRecusiveDeepEqual({}, {}) ||
!simpleRecusiveDeepEqual([], []) ||
!simpleRecusiveDeepEqual(['a'], ['a']) ||
// Not strict checks
!simpleRecusiveDeepEqual("1", 1) ||
// Multiple objects check
!simpleRecusiveDeepEqual( { a:[1,2] }, { a:[1,2] }, { a:[1,2] } ) ||
// Ensure distinction between array and object (the following should fail)
simpleRecusiveDeepEqual( [1963], { 0:1963 } ) ||
// Null strict checks
simpleRecusiveDeepEqual( 0, null ) ||
simpleRecusiveDeepEqual( "", null ) ||
// Last "false" exists to make the various check above easy to comment in/out
false
) {
alert("FATAL ERROR: simpleRecusiveDeepEqual failed basic checks");
} else {
//added this last line, for SO snippet alert on success
alert("simpleRecusiveDeepEqual: Passed all checks, Yays!");
}
})();
Ecco una versione del trucco stringify che è meno digitante e funziona in molti casi per banali confronti di dati JSON.
var obj1Fingerprint = JSON.stringify(obj1).replace(/\{|\}/g,'').split(',').sort().join(',');
var obj2Fingerprint = JSON.stringify(obj2).replace(/\{|\}/g,'').split(',').sort().join(',');
if ( obj1Fingerprint === obj2Fingerprint) { ... } else { ... }
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)
Per quelli di voi che usano NodeJS, esiste un metodo conveniente chiamato isDeepStrictEqual
sulla libreria Util nativa che può raggiungere questo obiettivo
const util = require('util');
const foo = {
hey: "ho",
lets: "go"
}
const bar = {
hey: "ho",
lets: "go"
}
foo == bar // false
util.isDeepStrictEqual(foo, bar) // true
https://nodejs.org/api/util.html#util_util_isdeepstricte2_val