Un opérateur d'égalité stricte vous dira si deux objets types sont égaux. Cependant, y a-t-il un moyen de savoir si deux objets sont égaux, un peu comme la valeur du code de hachage en Java?
Stack Overflow question Existe-t-il une sorte de fonction hashCode dans JavaScript? est similaire à cette question, mais nécessite une réponse plus théorique. Le scénario ci-dessus montre pourquoi il serait nécessaire d’en avoir un et je me demande s’il existe une solution équivalente.
La réponse courte
La réponse simple est: non, il n’existe aucun moyen générique de déterminer qu’un objet est égal à un autre au sens où vous l'entendez. L'exception est lorsque vous pensez strictement à un objet non typé.
La réponse longue
Le concept est celui d'une méthode Equals qui compare deux instances différentes d'un objet pour indiquer si elles sont égales au niveau de la valeur. Cependant, il appartient au type spécifique de définir comment une méthode Equals
doit être implémentée. Une comparaison itérative d'attributs ayant des valeurs primitives peut ne pas être suffisante, il peut également y avoir des attributs qui ne doivent pas être considérés comme faisant partie de la valeur de l'objet. Par exemple,
function MyClass(a, b)
{
var c;
this.getCLazy = function() {
if (c === undefined) c = a * b // imagine * is really expensive
return c;
}
}
Dans ce cas, c
n'est pas vraiment important pour déterminer si deux instances de MyClass sont égales, seuls a
et b
sont importants. Dans certains cas, c
peut varier d’une instance à l’autre et ne pas être significatif lors de la comparaison.
Notez que ce problème se pose lorsque les membres peuvent être eux-mêmes des instances d'un type et que chacun d'entre eux devrait avoir un moyen de déterminer l'égalité.
Ce qui complique encore les choses, c’est que la distinction entre données et méthode est floue en JavaScript.
Un objet peut faire référence à une méthode à appeler en tant que gestionnaire d'événements, ce qui ne serait probablement pas considéré comme faisant partie de son "état de valeur". Tandis qu'un autre objet peut très bien se voir attribuer une fonction qui effectue un calcul important et rend ainsi cette instance différente des autres simplement parce qu'elle fait référence à une fonction différente.
Qu'en est-il d'un objet dont l'une des méthodes de prototype existantes est remplacée par une autre fonction? Pouvait-il toujours être considéré comme égal à un autre cas qui serait autrement identique? Cette question ne peut être répondue dans chaque cas spécifique pour chaque type.
Comme indiqué précédemment, l'exception serait un objet strictement non typé. Dans ce cas, le seul choix sensé est une comparaison itérative et récursive de chaque membre. Même alors, il faut se demander quelle est la "valeur" d'une fonction?
Pourquoi réinventer la roue? Essayez Lodash . Il a un certain nombre de fonctions indispensables telles que isEqual () .
_.isEqual(object, other);
Comme chaque exemple sur cette page, il vérifiera brutalement la force de chaque valeur de clé à l’aide de ECMAScript 5 et d’optimisations natives si elles sont disponibles dans le navigateur.
Remarque: Auparavant, cette réponse recommandait Underscore.js , mais lodash a mieux réussi à corriger les bogues et à résoudre les problèmes de cohérence.
L'opérateur d'égalité par défaut dans JavaScript for Objects renvoie true lorsqu'il fait référence au même emplacement en mémoire.
var x = {};
var y = {};
var z = x;
x === y; // => false
x === z; // => true
Si vous avez besoin d'un autre opérateur d'égalité, vous devrez ajouter une méthode equals(other)
, ou quelque chose de similaire, à vos classes. Les détails de votre domaine de problèmes détermineront ce que cela signifie exactement.
Voici un exemple de carte à jouer:
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
Si vous travaillez dans AngularJS , la fonction angular.equals
déterminera si deux objets sont égaux. Dans Ember.js use isEqual
.
angular.equals
- Voir les docs ou source pour plus d’informations sur cette méthode. Il fait aussi une comparaison profonde sur les tableaux.isEqual
- Voir le -docs ou source pour plus d’informations sur cette méthode. Il ne fait pas une comparaison profonde sur les tableaux.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>
Ceci est ma version. Il utilise la nouvelle fonctionnalité Object.keys introduite dans ES5 et les idées/tests de + , + et + :
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 } }));
Si vous utilisez une bibliothèque JSON, vous pouvez coder chaque objet au format JSON, puis comparer les chaînes obtenues pour obtenir une égalité.
var obj1={test:"value"};
var obj2={test:"value2"};
alert(JSON.encode(obj1)===JSON.encode(obj2));
NOTE: Bien que cette réponse fonctionne dans de nombreux cas, comme plusieurs personnes l’ont souligné dans les commentaires, cela pose problème pour diverses raisons. Dans presque tous les cas, vous voudrez trouver une solution plus robuste.
Brève implémentation fonctionnelle deepEqual
:
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 : version 2, en utilisant les fonctions de suggestion de flèche et 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);
}
Si vous avez une fonction de copie en profondeur à portée de main, vous pouvez utiliser l’astuce suivante pour still utiliser JSON.stringify
en faisant correspondre l’ordre des propriétés:
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);
}
Démo: http://jsfiddle.net/CU3vb/3/
Raisonnement:
Les propriétés de obj1
étant copiées une par une sur le clone, leur ordre dans le clone sera conservé. Et lorsque les propriétés de obj2
sont copiées dans le clone, étant donné que les propriétés existantes dans obj1
seront simplement écrasées, leurs commandes dans le clone seront préservées.
Essayez-vous de vérifier si deux objets sont égaux? c'est à dire: leurs propriétés sont égales?
Si tel est le cas, vous aurez probablement remarqué cette situation:
var a = { foo : "bar" };
var b = { foo : "bar" };
alert (a == b ? "Equal" : "Not equal");
// "Not equal"
vous pourriez avoir à faire quelque chose comme ça:
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;
}
Évidemment, cette fonction pourrait nécessiter un peu d'optimisation et la possibilité d'effectuer une vérification approfondie (pour gérer les objets imbriqués: var a = { foo : { fu : "bar" } }
), mais vous en avez une idée.
Comme FOR l'a fait remarquer, vous devrez peut-être adapter cela à vos propres fins, par exemple: différentes classes peuvent avoir différentes définitions d'égalité. Si vous travaillez uniquement avec des objets simples, ce qui précède peut suffire. Sinon, une fonction personnalisée MyClass.equals()
peut s'avérer être la solution.
Dans Node.js, vous pouvez utiliser sa require("assert").deepEqual
native. Plus d'infos: http://nodejs.org/api/assert.html
Par exemple:
var assert = require("assert");
assert.deepEqual({a:1, b:2}, {a:1, b:3}); // will throw AssertionError
Un autre exemple qui renvoie true
/false
au lieu de renvoyer des erreurs:
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;
};
J'utilise cette fonction comparable
pour produire des copies de mes objets comparables à 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>
Pratique dans les tests (la plupart des frameworks de test ont une fonction is
). Par exemple.
is(JSON.stringify(comparable(x)), JSON.stringify(comparable(y)), 'x must match y');
Si une différence est détectée, les chaînes sont enregistrées, ce qui permet de différencier les différences:
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}}.
Heres est une solution ES6/ES2015 utilisant une approche de style fonctionnel:
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;
}
}
Une solution simple à ce problème que beaucoup de gens ne réalisent pas consiste à trier les chaînes JSON (par caractère). C’est aussi généralement plus rapide que les autres solutions mentionnées ici:
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(''));
}
Une autre chose utile à propos de cette méthode est que vous pouvez filtrer les comparaisons en passant une fonction "replacer" aux fonctions JSON.stringify ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify # Example_of_using_replacer_parameter ). Ce qui suit ne comparera que toutes les clés d'objets nommées "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;
});
vous pouvez utiliser _.isEqual(obj1, obj2)
depuis la bibliothèque underscore.js.
Voici un exemple:
var stooge = {name: 'moe', luckyNumbers: [13, 27, 34]};
var clone = {name: 'moe', luckyNumbers: [13, 27, 34]};
stooge == clone;
=> false
_.isEqual(stooge, clone);
=> true
Voir la documentation officielle à partir d'ici: http://underscorejs.org/#isEqual
Je ne sais pas si quelqu'un a posté quelque chose de similaire à cela, mais voici une fonction que j'ai créée pour vérifier les égalités d'objet.
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;
}
En outre, il est récursif, de sorte qu'il peut également vérifier la profonde égalité, si c'est ainsi que vous l'appelez.
Si vous utilisez ES6 + via Babel ou autrement, vous pouvez également utiliser Object.is(x, y)
.
Référence: http://wiki.ecmascript.org/doku.php?id=harmony:egal#object.is_x_y
Si vous comparez des objets JSON, vous pouvez utiliser https://github.com/mirek/node-rus-diff
npm install rus-diff
Usage:
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 } }
Si deux objets sont différents, un objet similaire à {$rename:{...}, $unset:{...}, $set:{...}}
compatible avec MongoDB est renvoyé.
Je déconseille le hachage ou la sérialisation (comme le suggère la solution JSON). Si vous devez tester si deux objets sont égaux, vous devez définir ce que signifie "égal". Il se peut que tous les membres de données dans les deux objets correspondent, ou que les emplacements de mémoire doivent correspondre (ce qui signifie que les deux variables référencent le même objet en mémoire), ou qu’un seul membre de données dans chaque objet doit correspondre.
Récemment, j'ai développé un objet dont le constructeur crée un nouvel identifiant (en commençant par 1 et en l'incrémentant de 1) chaque fois qu'une instance est créée. Cet objet a une fonction isEqual qui compare cette valeur id à la valeur id d'un autre objet et renvoie true si elles correspondent.
Dans ce cas, j'ai défini "égal" comme signifiant que les valeurs id correspondent. Étant donné que chaque instance a un identifiant unique, ceci pourrait être utilisé pour renforcer l’idée que les objets correspondants occupent également le même emplacement mémoire. Bien que ce ne soit pas nécessaire.
Ayant besoin d’une fonction de comparaison d’objets plus générique que celle affichée, j’ai concocté ce qui suit. Critique appréciée ...
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;
}
En sortant de ma bibliothèque personnelle, que j'utilise régulièrement pour mon travail. La fonction suivante est une égalité profonde récursive clémente, que ne vérifie pas
J'utilise principalement ceci pour vérifier si je reçois des réponses égales par rapport à diverses implémentations d'API. Où différence d'implémentation (comme chaîne contre nombre) et des valeurs null supplémentaires, peuvent se produire.
Sa mise en œuvre est assez simple et courte (si tous les commentaires sont supprimés)
/** 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 [email protected], 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!");
}
})();
J'ai fait face au même problème et j'ai décidé d'écrire ma propre solution. Mais parce que je veux aussi comparer les tableaux avec les objets et vice-versa, j'ai conçu une solution générique. J'ai décidé d'ajouter les fonctions au prototype, mais on peut facilement les réécrire en fonctions autonomes. Voici le code:
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;
}
Cet algorithme est divisé en deux parties; La fonction equals elle-même et une fonction permettant de trouver l'index numérique d'une propriété dans un tableau/objet. La fonction de recherche est uniquement nécessaire car indexof ne trouve que des nombres et des chaînes et aucun objet.
On peut l'appeler comme ça:
({a: 1, b: "h"}).equals({a: 1, b: "h"});
La fonction renvoie soit true soit false, dans ce cas true . L'algorithme permet également de comparer des objets très complexes:
({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'exemple supérieur retournera vrai, même si les propriétés ont un ordre différent. Un petit détail à surveiller: ce code vérifie également le même type de deux variables, donc "3" n’est pas le même que 3.
Je voulais juste contribuer à ma version de la comparaison d'objets en utilisant certaines fonctionnalités de es6. Il ne prend pas en compte une commande. Après avoir converti tous les if/else en ternary, je suis venu avec ce qui suit:
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;
}
)
}
Il est utile de considérer deux objets égaux s'ils ont toutes les mêmes valeurs pour toutes les propriétés et de manière récursive pour tous les objets et tableaux imbriqués. Je considère également que les deux objets suivants sont égaux:
var a = {p1: 1};
var b = {p1: 1, p2: undefined};
De même, les tableaux peuvent avoir des éléments "manquants" et des éléments non définis. Je les traiterais de la même façon:
var c = [1, 2];
var d = [1, 2, undefined];
Une fonction qui implémente cette définition d'égalité:
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;
}
Code source (y compris les fonctions d'assistance, generalType et uniqueArray): Test unitaire et Tester Runner ici .
Il existe un correctif très simple pour celui-ci, tout ce que vous avez à faire est JSON.stringify () sur les deux objets lorsque vous les comparez.
Ceci est un ajout pour tout ce qui précède, pas un remplacement. Si vous avez besoin de comparer rapidement des objets peu profonds sans avoir à vérifier les cas récursifs supplémentaires. Voici un coup de feu.
À comparer: 1) égalité du nombre de propriétés propres, 2) égalité des noms de clé, 3) si bCompareValues == true, égalité des valeurs de propriété correspondantes et de leurs types (triple égalité)
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;
}
Voici une version de l'astuce stringify qui nécessite moins de frappe et fonctionne dans de nombreux cas pour des comparaisons de données JSON triviales.
var obj1Fingerprint = JSON.stringify(obj1).replace(/\{|\}/g,'').split(',').sort().join(',');
var obj2Fingerprint = JSON.stringify(obj2).replace(/\{|\}/g,'').split(',').sort().join(',');
if ( obj1Fingerprint === obj2Fingerprint) { ... } else { ... }
Je fais les hypothèses suivantes avec cette fonction:
Cela devrait être traité comme une démonstration d'une stratégie simple.
/**
* 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;
}
Je sais que c'est un peu vieux, mais je voudrais ajouter une solution que j'ai proposée pour ce problème… J'ai eu un objet et je voulais savoir quand ses données ont changé. "quelque chose de similaire à Object.observe" et ce que j'ai fait était:
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)
}
Ceci peut être dupliqué ici et créer un autre ensemble de tableaux pour comparer les valeurs et les clés ... C'est très simple, car ils sont maintenant des tableaux et renverront false si les objets ont des tailles différentes.
Pour comparer des clés pour des instances d'objet simples paires paire clé/valeur, j'utilise:
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);
};
Une fois les clés comparées, une simple boucle supplémentaire for..in
suffit.
La complexité est O (N * N), N étant le nombre de clés.
J'espère/suppose que les objets que je définis ne contiendront pas plus de 1000 propriétés ...
Cela dépend de ce que vous entendez par égalité. C'est donc à vous, développeur des classes, de définir leur égalité.
Un cas est parfois utilisé, où deux instances sont considérées comme «égales» si elles pointent vers le même emplacement en mémoire, mais ce n'est pas toujours ce que vous voulez. Par exemple, si j'ai une classe Person, je pourrais vouloir considérer deux objets Person «égaux» s'ils ont le même nom, le même prénom et le même numéro de sécurité sociale (même s'ils pointent vers des emplacements différents en mémoire).
D'autre part, nous ne pouvons pas simplement dire que deux objets sont égaux si la valeur de chacun de leurs membres est la même, car, parfois, vous ne voulez pas cela. En d'autres termes, pour chaque classe, il appartient au développeur de la classe de définir les membres qui constituent l'identité des objets et de développer un opérateur d'égalité approprié (que ce soit en surchargeant l'opérateur == ou en utilisant la méthode Equals).
Dire que deux objets sont égaux s'ils ont le même hachage est une issue. Cependant, vous devez ensuite vous demander comment le hachage est calculé pour chaque instance. Pour revenir à l'exemple de personne ci-dessus, nous pourrions utiliser ce système si le hachage a été calculé en consultant les valeurs des champs Prénom, Nom et Numéro de sécurité sociale. De plus, nous nous basons sur la qualité de la méthode de hachage (c’est un sujet énorme, mais il suffit de dire que tous les hachages ne sont pas créés égaux et que de mauvaises méthodes de hachage peuvent conduire à more les collisions qui, dans ce cas, renverraient de fausses correspondances).
Je vois des réponses de code spaghetti… .. Sans utiliser de bibliothèque tierce, c'est très simple.
Commencez par trier les deux objets par leur nom.
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)
Ensuite, utilisez simplement une chaîne pour les comparer.
JSON.stringify(objectOne) === JSON.stringify(objectTwo)
En fonction, dépendemment. Si l'ordre des clés dans l'objet n'a pas d'importance, et je n'ai pas besoin de connaître les prototypes dudit objet. Utiliser toujours faire le travail.
const object = {};
JSON.stringify(object) === "{}" will pass but {} === "{}" will not
Je ne suis pas un expert en Javascript, mais voici une tentative simple pour le résoudre. Je vérifie trois choses:
object
et aussi que ce n'est pas null
car typeof null
est object
.function deepEqual (first, second) {
// Not equal if either is not an object or is null.
if (!isObject(first) || !isObject(second) ) return false;
// If properties count is different
if (keys(first).length != keys(second).length) return false;
// Return false if any property value is different.
for(prop in first){
if (first[prop] != second[prop]) return false;
}
return true;
}
// Checks if argument is an object and is not null
function isObject(obj) {
return (typeof obj === "object" && obj != null);
}
// returns arrays of object keys
function keys (obj) {
result = [];
for(var key in obj){
result.Push(key);
}
return result;
}
// Some test code
obj1 = {
name: 'Singh',
age: 20
}
obj2 = {
age: 20,
name: 'Singh'
}
obj3 = {
name: 'Kaur',
age: 19
}
console.log(deepEqual(obj1, obj2));
console.log(deepEqual(obj1, obj3));
Voici une approche très basique pour vérifier "l'égalité de valeur" d'un objet.
var john = {
occupation: "Web Developer",
age: 25
};
var bobby = {
occupation: "Web Developer",
age: 25
};
function isEquivalent(a, b) {
// Create arrays of property names
var aProps = Object.getOwnPropertyNames(a);
var bProps = Object.getOwnPropertyNames(b);
// If number of properties is different, objects are not equivalent
if (aProps.length != bProps.length) {
return false;
}
for (var i = 0; i < aProps.length; i++) {
var propName = aProps[i];
// If values of same property are not equal, objects are not equivalent
if (a[propName] !== b[propName]) {
return false;
}
}
// If we made it this far, objects are considered equivalent
return true;
}
// Outputs: true
console.log(isEquivalent(john, bobby));
Comme vous pouvez le constater, pour vérifier "l'égalité des valeurs" des objets, nous devons essentiellement parcourir chaque propriété des objets pour voir si elles sont égales. Et bien que cette implémentation simple fonctionne pour notre exemple, il y a beaucoup de cas qu'elle ne gère pas. Par exemple:
Pour une méthode robuste de vérification de "l'égalité de valeur" des objets, il est préférable de s'appuyer sur une bibliothèque bien testée qui couvre les différents cas Edge comme Underscore .
var john = {
occupation: "Web Developer",
age: 25
};
var bobby = {
occupation: "Web Developer",
age: 25
};
// Outputs: true
console.log(_.isEqual(john, bobby));
function isDeepEqual(obj1, obj2, testPrototypes = false) {
if (obj1 === obj2) {
return true
}
if (typeof obj1 === "function" && typeof obj2 === "function") {
return obj1.toString() === obj2.toString()
}
if (obj1 instanceof Date && obj2 instanceof Date) {
return obj1.getTime() === obj2.getTime()
}
if (
Object.prototype.toString.call(obj1) !==
Object.prototype.toString.call(obj2) ||
typeof obj1 !== "object"
) {
return false
}
const prototypesAreEqual = testPrototypes
? isDeepEqual(
Object.getPrototypeOf(obj1),
Object.getPrototypeOf(obj2),
true
)
: true
const obj1Props = Object.getOwnPropertyNames(obj1)
const obj2Props = Object.getOwnPropertyNames(obj2)
return (
obj1Props.length === obj2Props.length &&
prototypesAreEqual &&
obj1Props.every(prop => isDeepEqual(obj1[prop], obj2[prop]))
)
}
console.log(isDeepEqual({key: 'one'}, {key: 'first'}))
console.log(isDeepEqual({key: 'one'}, {key: 'one'}))
J'ai écrit une petite bibliothèque qui fonctionne sur Node.js et le navigateur appelé compare.js. Il offre les opérateurs de comparaison habituels, tels que ==,! =,>,> =, <, <= Et identité sur tous les types de données de JavaScript.
Par exemple, vous pouvez utiliser
cmp.eq(obj1, obj2);
et cela vérifiera l'égalité (en utilisant une approche d'égalité profonde). Sinon, si vous le faites
cmp.id(obj1, obj2);
il va comparer par référence, donc vérifier l'identité .. vous pouvez également utiliser <et> sur des objets, ce qui signifie sous-ensemble et sur-ensemble.
compare.js est couvert par près de 700 tests unitaires, il ne devrait donc pas y avoir trop de bugs ;-).
Vous pouvez le trouver sur https://github.com/goloroden/compare.js gratuitement, il est à code source ouvert sous la licence MIT.
function isEqual(obj1, obj2){
type1 = typeof(obj1);
type2 = typeof(obj2);
if(type1===type2){
switch (type1){
case "object": return JSON.stringify(obj1)===JSON.stringify(obj2);
case "function": return eval(obj1).toString()===eval(obj2).toString();
default: return obj1==obj2;
}
}
return false;
}//have not tried but should work.
let user1 = {
name: "John",
address: {
line1: "55 Green Park Road",
line2: {
a:[1,2,3]
}
},
email:null
}
let user2 = {
name: "John",
address: {
line1: "55 Green Park Road",
line2: {
a:[1,2,3]
}
},
email:null
}
// Method 1
function isEqual(a, b) {
return JSON.stringify(a) === JSON.stringify(b);
}
// Method 2
function isEqual(a, b) {
// checking type of a And b
if(typeof a !== 'object' || typeof b !== 'object') {
return false;
}
// Both are NULL
if(!a && !b ) {
return true;
} else if(!a || !b) {
return false;
}
let keysA = Object.keys(a);
let keysB = Object.keys(b);
if(keysA.length !== keysB.length) {
return false;
}
for(let key in a) {
if(!(key in b)) {
return false;
}
if(typeof a[key] === 'object') {
if(!isEqual(a[key], b[key]))
{
return false;
}
} else {
if(a[key] !== b[key]) {
return false;
}
}
}
return true;
}
console.log(isEqual(user1,user2));
Vérification de l'égalité d'objet: JSON.stringify(array1.sort()) === JSON.stringify(array2.sort())
Le test ci-dessus fonctionne également avec des tableaux d'objets, auquel cas une fonction de tri est décrite dans http://www.w3schools.com/jsref/jsref_sort.asp
Cela pourrait suffire pour les petits tableaux contenant des schémas JSON plats.
Un "hack" rapide pour savoir si deux objets sont similaires, consiste à utiliser leurs méthodes toString (). Si vous vérifiez les objets A et B, assurez-vous que A et B ont des méthodes significatives toString () et vérifiez que les chaînes qu'elles renvoient sont identiques.
Ce n'est pas une panacée, mais cela peut être utile parfois dans les bonnes situations.
Je dois me moquer des requêtes jQuery POST. L'égalité qui compte pour moi est que les deux objets ont le même ensemble de propriétés (aucune ne manque dans aucun définition). Je me moque des objets ayant des méthodes incompatibles.
Voici ce que je vais utiliser, il devrait suffire à satisfaire les {mes spécificités} _:
function PostRequest() {
for (var i = 0; i < arguments.length; i += 2) {
this[arguments[i]] = arguments[i+1];
}
var compare = function(u, v) {
if (typeof(u) != typeof(v)) {
return false;
}
var allkeys = {};
for (var i in u) {
allkeys[i] = 1;
}
for (var i in v) {
allkeys[i] = 1;
}
for (var i in allkeys) {
if (u.hasOwnProperty(i) != v.hasOwnProperty(i)) {
if ((u.hasOwnProperty(i) && typeof(u[i]) == 'function') ||
(v.hasOwnProperty(i) && typeof(v[i]) == 'function')) {
continue;
} else {
return false;
}
}
if (typeof(u[i]) != typeof(v[i])) {
return false;
}
if (typeof(u[i]) == 'object') {
if (!compare(u[i], v[i])) {
return false;
}
} else {
if (u[i] !== v[i]) {
return false;
}
}
}
return true;
};
this.equals = function(o) {
return compare(this, o);
};
return this;
}
Utilisez comme si:
foo = new PostRequest('text', 'hello', 'html', '<p>hello</p>');
foo.equals({ html: '<p>hello</p>', text: 'hello' });
Est-ce acceptable?
deepEqual = (x, y) => {
let areEqual = false;
const Obj = Object.keys(x);
const keysSize = Obj.length;
let counter = 0;
Obj.forEach(key => {
if (y[key] === x[key]) {
counter += 1;
}
});
if (counter === keysSize) areEqual = true;
return areEqual;
};
J'ai implémenté une méthode qui prend deux jsons et vérifie si leurs clés ont les mêmes valeurs en utilisant la récursivité. J'ai utilisé une autre question pour résoudre ceci.
const arraysEqual = (a, b) => {
if (a === b)
return true;
if (a === null || b === null)
return false;
if (a.length !== b.length)
return false;
// If you don't care about the order of the elements inside
// the array, you should sort both arrays here.
for (let i = 0; i < a.length; ++i) {
if (a[i] !== b[i])
return false;
}
return true;
};
const jsonsEqual = (a, b) => {
if(typeof a !== 'object' || typeof b !== 'object')
return false;
if (Object.keys(a).length === Object.keys(b).length) { // if items have the same size
let response = true;
for (let key in a) {
if (!b[key]) // if not key
response = false;
if (typeof a[key] !== typeof b[key]) // if typeof doesn't equals
response = false;
else {
if (Array.isArray(a[key])) // if array
response = arraysEqual(a[key], b[key]);
else if (typeof a[key] === 'object') // if another json
response = jsonsEqual(a[key], b[key]);
else if (a[key] !== b[key]) // not equals
response = false;
}
if (!response) // return if one item isn't equal
return false;
}
} else
return false;
return true;
};
const json1 = {
a: 'a',
b: 'asd',
c: [
'1',
2,
2.5,
'3',
{
d: 'asd',
e: [
1.6,
{
f: 'asdasd',
g: '123'
}
]
}
],
h: 1,
i: 1.2,
};
const json2 = {
a: 'nops',
b: 'asd'
};
const json3 = {
a: 'h',
b: '484',
c: [
3,
4.5,
'2ss',
{
e: [
{
f: 'asdasd',
g: '123'
}
]
}
],
h: 1,
i: 1.2,
};
const result = jsonsEqual(json1,json2);
//const result = jsonsEqual(json1,json3);
//const result = jsonsEqual(json1,json1);
if(result) // is equal
$('#result').text("Jsons are the same")
else
$('#result').text("Jsons aren't equals")
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="result"></div>
JSON.stringify () fonctionne pour les deux types d’objets profonds et non profonds, pas très sûr des aspects de performance:
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));
Oui, une autre réponse ...
Object.prototype.equals = function (object) {
if (this.constructor !== object.constructor) return false;
if (Object.keys(this).length !== Object.keys(object).length) return false;
var obk;
for (obk in object) {
if (this[obk] !== object[obk])
return false;
}
return true;
}
var aaa = JSON.parse('{"name":"mike","tel":"1324356584"}');
var bbb = JSON.parse('{"tel":"1324356584","name":"mike"}');
var ccc = JSON.parse('{"name":"mike","tel":"584"}');
var ddd = JSON.parse('{"name":"mike","tel":"1324356584", "work":"nope"}');
$("#ab").text(aaa.equals(bbb));
$("#ba").text(bbb.equals(aaa));
$("#bc").text(bbb.equals(ccc));
$("#ad").text(aaa.equals(ddd));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
aaa equals bbb? <span id="ab"></span> <br/>
bbb equals aaa? <span id="ba"></span> <br/>
bbb equals ccc? <span id="bc"></span> <br/>
aaa equals ddd? <span id="ad"></span>
J'ai une fonction beaucoup plus courte qui va profondément dans tous les sous-objets ou tableaux. Il est aussi efficace que JSON.stringify(obj1) === JSON.stringify(obj2)
mais JSON.stringify
ne fonctionnera pas si la commande n’est pas la même ( comme mentionné ici ).
var obj1 = { a : 1, b : 2 };
var obj2 = { b : 2, a : 1 };
console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // false
La fonction serait également un bon début si vous voulez faire quelque chose avec les valeurs inégales.
function arr_or_obj(v)
{ return !!v && (v.constructor === Object || v.constructor === Array); }
function deep_equal(v1, v2)
{
if (arr_or_obj(v1) && arr_or_obj(v2) && v1.constructor === v2.constructor)
{
if (Object.keys(v1).length === Object.keys(v2).length) // check the length
for (var i in v1)
{
if (!deep_equal(v1[i], v2[i]))
{ return false; }
}
else
{ return false; }
}
else if (v1 !== v2)
{ return false; }
return true;
}
//////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////
var obj1 = [
{
hat : {
cap : ['something', null ],
helmet : [ 'triple eight', 'pro-tec' ]
},
shoes : [ 'loafer', 'penny' ]
},
{
beers : [ 'budweiser', 'busch' ],
wines : [ 'barefoot', 'yellow tail' ]
}
];
var obj2 = [
{
shoes : [ 'loafer', 'penny' ], // same even if the order is different
hat : {
cap : ['something', null ],
helmet : [ 'triple eight', 'pro-tec' ]
}
},
{
beers : [ 'budweiser', 'busch' ],
wines : [ 'barefoot', 'yellow tail' ]
}
];
console.log(deep_equal(obj1, obj2)); // true
console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // false
console.log(deep_equal([], [])); // true
console.log(deep_equal({}, {})); // true
console.log(deep_equal([], {})); // false
Et si vous souhaitez ajouter le support pour Function
, Date
et RegExp
, vous pouvez l'ajouter au début de deep_equal
(non testé):
if ((typeof obj1 === 'function' && typeof obj2 === 'function') ||
(obj1 instanceof Date && obj2 instanceof Date) ||
(obj1 instanceof RegExp && obj2 instanceof RegExp))
{
obj1 = obj1.toString();
obj2 = obj2.toString();
}
Pour ceux qui utilisent NodeJS, il existe une méthode pratique appelée isDeepStrictEqual
sur la bibliothèque Util native qui permet d’atteindre cet objectif.
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_isdeepstrictequal_val1_val2
Bien sûr, pendant que nous y sommes, je vais réinventer ma propre roue (je suis fier du nombre de rayons et de matériaux utilisés):
////////////////////////////////////////////////////////////////////////////////
var equals = function ( objectA, objectB ) {
var result = false,
keysA,
keysB;
// Check if they are pointing at the same variable. If they are, no need to test further.
if ( objectA === objectB ) {
return true;
}
// Check if they are the same type. If they are not, no need to test further.
if ( typeof objectA !== typeof objectB ) {
return false;
}
// Check what kind of variables they are to see what sort of comparison we should make.
if ( typeof objectA === "object" ) {
// Check if they have the same constructor, so that we are comparing apples with apples.
if ( objectA.constructor === objectA.constructor ) {
// If we are working with Arrays...
if ( objectA instanceof Array ) {
// Check the arrays are the same length. If not, they cannot be the same.
if ( objectA.length === objectB.length ) {
// Compare each element. They must be identical. If not, the comparison stops immediately and returns false.
return objectA.every(
function ( element, i ) {
return equals( element, objectB[ i ] );
}
);
}
// They are not the same length, and so are not identical.
else {
return false;
}
}
// If we are working with RegExps...
else if ( objectA instanceof RegExp ) {
// Return the results of a string comparison of the expression.
return ( objectA.toString() === objectB.toString() );
}
// Else we are working with other types of objects...
else {
// Get the keys as arrays from both objects. This uses Object.keys, so no old browsers here.
keysA = Object.keys( objectA );
keysB = Object.keys( objectB );
// Check the key arrays are the same length. If not, they cannot be the same.
if ( keysA.length === keysB.length ) {
// Compare each property. They must be identical. If not, the comparison stops immediately and returns false.
return keysA.every(
function ( element ) {
return equals( objectA[ element ], objectB[ element ] );
}
);
}
// They do not have the same number of keys, and so are not identical.
else {
return false;
}
}
}
// They don't have the same constructor.
else {
return false;
}
}
// If they are both functions, let us do a string comparison.
else if ( typeof objectA === "function" ) {
return ( objectA.toString() === objectB.toString() );
}
// If a simple variable type, compare directly without coercion.
else {
return ( objectA === objectB );
}
// Return a default if nothing has already been returned.
return result;
};
////////////////////////////////////////////////////////////////////////////////
Cela retourne faux le plus rapidement possible, mais bien sûr, pour un gros objet où la différence est profondément imbriquée, il pourrait être moins efficace. Dans mon propre scénario, une bonne gestion des tableaux imbriqués est importante.
J'espère que cela aidera quelqu'un qui a besoin de ce genre de «roue».
Certaines des solutions suivantes présentent des problèmes de performances, de fonctionnalités et de style ... Elles ne sont pas suffisamment pensées et certaines échouent dans différents cas. J'ai essayé de résoudre ce problème avec ma propre solution et j'apprécierais beaucoup vos commentaires:
http://stamat.wordpress.com/javascript-object-comparison/
//Returns the object's class, Array, Date, RegExp, Object are of interest to us
var getClass = function(val) {
return Object.prototype.toString.call(val)
.match(/^\[object\s(.*)\]$/)[1];
};
//Defines the type of the value, extended typeof
var whatis = function(val) {
if (val === undefined)
return 'undefined';
if (val === null)
return 'null';
var type = typeof val;
if (type === 'object')
type = getClass(val).toLowerCase();
if (type === 'number') {
if (val.toString().indexOf('.') > 0)
return 'float';
else
return 'integer';
}
return type;
};
var compareObjects = function(a, b) {
if (a === b)
return true;
for (var i in a) {
if (b.hasOwnProperty(i)) {
if (!equal(a[i],b[i])) return false;
} else {
return false;
}
}
for (var i in b) {
if (!a.hasOwnProperty(i)) {
return false;
}
}
return true;
};
var compareArrays = function(a, b) {
if (a === b)
return true;
if (a.length !== b.length)
return false;
for (var i = 0; i < a.length; i++){
if(!equal(a[i], b[i])) return false;
};
return true;
};
var _equal = {};
_equal.array = compareArrays;
_equal.object = compareObjects;
_equal.date = function(a, b) {
return a.getTime() === b.getTime();
};
_equal.regexp = function(a, b) {
return a.toString() === b.toString();
};
// uncoment to support function as string compare
// _equal.fucntion = _equal.regexp;
/*
* Are two values equal, deep compare for objects and arrays.
* @param a {any}
* @param b {any}
* @return {boolean} Are equal?
*/
var equal = function(a, b) {
if (a !== b) {
var atype = whatis(a), btype = whatis(b);
if (atype === btype)
return _equal.hasOwnProperty(atype) ? _equal[atype](a, b) : a==b;
return false;
}
return true;
};
Voici une fonction de vérificateur d'égalité générique qui reçoit un tableau d'éléments en entrée et les compare. Fonctionne avec tous les types d'éléments.
const isEqual = function(inputs = []) {
// Checks an element if js object.
const isObject = function(data) {
return Object.prototype.toString.call(data) === '[object Object]';
};
// Sorts given object by its keys.
const sortObjectByKey = function(obj) {
const self = this;
if (!obj) return {};
return Object.keys(obj).sort().reduce((initialVal, item) => {
initialVal[item] = !Array.isArray(obj[item]) &&
typeof obj[item] === 'object'
? self.objectByKey(obj[item])
: obj[item];
return initialVal;
}, {});
};
// Checks equality of all elements in the input against each other. Returns true | false
return (
inputs
.map(
input =>
typeof input == 'undefined'
? ''
: isObject(input)
? JSON.stringify(sortObjectByKey(input))
: JSON.stringify(input)
)
.reduce(
(prevValue, input) =>
prevValue === '' || prevValue === input ? input : false,
''
) !== false
);
};
// Tests (Made with Jest test framework.)
test('String equality check', () => {
expect(isEqual(['murat'])).toEqual(true);
expect(isEqual(['murat', 'john', 'doe'])).toEqual(false);
expect(isEqual(['murat', 'murat', 'murat'])).toEqual(true);
});
test('Float equality check', () => {
expect(isEqual([7.89, 3.45])).toEqual(false);
expect(isEqual([7, 7.50])).toEqual(false);
expect(isEqual([7.50, 7.50])).toEqual(true);
expect(isEqual([7, 7])).toEqual(true);
expect(isEqual([0.34, 0.33])).toEqual(false);
expect(isEqual([0.33, 0.33])).toEqual(true);
});
test('Array equality check', () => {
expect(isEqual([[1, 2, 3], [1, 2, 3]])).toEqual(true);
expect(isEqual([[1, 3], [1, 2, 3]])).toEqual(false);
expect(isEqual([['murat', 18], ['murat', 18]])).toEqual(true);
});
test('Object equality check', () => {
let obj1 = {
name: 'murat',
age: 18
};
let obj2 = {
name: 'murat',
age: 18
};
let obj3 = {
age: 18,
name: 'murat'
};
let obj4 = {
name: 'murat',
age: 18,
occupation: 'nothing'
};
expect(isEqual([obj1, obj2])).toEqual(true);
expect(isEqual([obj1, obj2, obj3])).toEqual(true);
expect(isEqual([obj1, obj2, obj3, obj4])).toEqual(false);
});
test('Weird equality checks', () => {
expect(isEqual(['', {}])).toEqual(false);
expect(isEqual([0, '0'])).toEqual(false);
});
Voici une jolie version CoffeeScript qui explique comment faire ceci:
Object::equals = (other) ->
typeOf = Object::toString
return false if typeOf.call(this) isnt typeOf.call(other)
return `this == other` unless typeOf.call(other) is '[object Object]' or
typeOf.call(other) is '[object Array]'
(return false unless this[key].equals other[key]) for key, value of this
(return false if typeof this[key] is 'undefined') for key of other
true
Voici les tests:
describe "equals", ->
it "should consider two numbers to be equal", ->
assert 5.equals(5)
it "should consider two empty objects to be equal", ->
assert {}.equals({})
it "should consider two objects with one key to be equal", ->
assert {a: "banana"}.equals {a: "banana"}
it "should consider two objects with keys in different orders to be equal", ->
assert {a: "banana", kendall: "garrus"}.equals {kendall: "garrus", a: "banana"}
it "should consider two objects with nested objects to be equal", ->
assert {a: {fruit: "banana"}}.equals {a: {fruit: "banana"}}
it "should consider two objects with nested objects that are jumbled to be equal", ->
assert {a: {a: "banana", kendall: "garrus"}}.equals {a: {kendall: "garrus", a: "banana"}}
it "should consider two objects with arrays as values to be equal", ->
assert {a: ["Apple", "banana"]}.equals {a: ["Apple", "banana"]}
it "should not consider an object to be equal to null", ->
assert !({a: "banana"}.equals null)
it "should not consider two objects with different keys to be equal", ->
assert !({a: "banana"}.equals {})
it "should not consider two objects with different values to be equal", ->
assert !({a: "banana"}.equals {a: "grapefruit"})
Beaucoup de bonnes pensées ici! Voici ma version de deep égal. Je l'ai posté sur github et j'ai écrit quelques tests autour de ça. Il est difficile de couvrir tous les cas possibles et parfois, il est inutile de le faire.
J'ai couvert NaN !== NaN
ainsi que des dépendances circulaires.
https://github.com/ryancat/simple-deep-equal/blob/master/index.js
Ma version, qui inclut la chaîne de l'endroit où se trouve la différence et quelle est la différence.
function DeepObjectCompare(O1, O2)
{
try {
DOC_Val(O1, O2, ['O1->O2', O1, O2]);
return DOC_Val(O2, O1, ['O2->O1', O1, O2]);
} catch(e) {
console.log(e.Chain);
throw(e);
}
}
function DOC_Error(Reason, Chain, Val1, Val2)
{
this.Reason=Reason;
this.Chain=Chain;
this.Val1=Val1;
this.Val2=Val2;
}
function DOC_Val(Val1, Val2, Chain)
{
function DoThrow(Reason, NewChain) { throw(new DOC_Error(Reason, NewChain!==undefined ? NewChain : Chain, Val1, Val2)); }
if(typeof(Val1)!==typeof(Val2))
return DoThrow('Type Mismatch');
if(Val1===null || Val1===undefined)
return Val1!==Val2 ? DoThrow('Null/undefined mismatch') : true;
if(Val1.constructor!==Val2.constructor)
return DoThrow('Constructor mismatch');
switch(typeof(Val1))
{
case 'object':
for(var m in Val1)
{
if(!Val1.hasOwnProperty(m))
continue;
var CurChain=Chain.concat([m]);
if(!Val2.hasOwnProperty(m))
return DoThrow('Val2 missing property', CurChain);
DOC_Val(Val1[m], Val2[m], CurChain);
}
return true;
case 'number':
if(Number.isNaN(Val1))
return !Number.isNaN(Val2) ? DoThrow('NaN mismatch') : true;
case 'string':
case 'boolean':
return Val1!==Val2 ? DoThrow('Value mismatch') : true;
case 'function':
if(Val1.prototype!==Val2.prototype)
return DoThrow('Prototype mismatch');
if(Val1!==Val2)
return DoThrow('Function mismatch');
return true;
default:
return DoThrow('Val1 is unknown type');
}
}
Voici ma solution à ce problème. Je ne considère pas le mien super, mais cela fonctionne sur la comparaison d'objet à tout type
Object.prototype.fullMatch = function(obj){
if (typeof this !== typeof obj) return false;
if (this == null && obj != null || this != null && obj == null) return false;
var this_keys = [];
var obj_keys = [];
for (var key in this) if (this.hasOwnProperty(key)) this_keys.Push(key);
for (var key in obj) if (obj.hasOwnProperty(key)) obj_keys.Push(key);
if (this_keys.length !== obj_keys.length){
this_keys = null;
obj_keys = null;
return false;
}
var full_match = true;
for (var key in this){
if (this.hasOwnProperty(key) && obj.hasOwnProperty(key)){
var this_value = this[key];
var obj_value = obj[key];
if (typeof this_value !== typeof obj_value || ("object" === typeof this_value && !this_value.fullMatch(obj_value)) || "object" !== typeof this_value && this_value !== obj_value){
full_match = false;
break;
}
}
}
return full_match;
};