Jak ustalić równość dla dwóch obiektów JavaScript?


669

Operator ścisłej równości powie ci, czy dwa typy obiektów są równe. Czy istnieje jednak sposób, aby stwierdzić, czy dwa obiekty są równe, podobnie jak wartość kodu skrótu w Javie?

Pytanie o przepełnienie stosu Czy w JavaScript jest jakaś funkcja hashCode? jest podobny do tego pytania, ale wymaga bardziej akademickiej odpowiedzi. Powyższy scenariusz pokazuje, dlaczego taki miałby być konieczny, i zastanawiam się, czy istnieje jakieś równoważne rozwiązanie .


3
Spójrz także na to pytanie stackoverflow.com/q/1068834/1671639
Praveen

37
Należy zauważyć, że nawet w Javie, a.hashCode() == b.hashCode()czy nie oznacza to, że ajest równa b. Jest to warunek konieczny, niewystarczający.
Heinzi


2
Jeśli MUSISZ porównać obiekty w kodzie, prawdopodobnie źle piszesz kod. Lepszym pytaniem może być: „Jak napisać ten kod, aby nie musiałem porównywać obiektów?”
th317erd

3
@ th317erd czy możesz wyjaśnić siebie? ...
El Mac

Odpowiedzi:


175

Krótka odpowiedź

Prosta odpowiedź brzmi: nie, nie ma ogólnych sposobów na ustalenie, że obiekt jest równy drugiemu w sensie, który masz na myśli. Wyjątkiem jest sytuacja, gdy myślisz ściśle o tym, że obiekt nie jest typem.

Długa odpowiedź

Chodzi o metodę Equals, która porównuje dwa różne wystąpienia obiektu, aby wskazać, czy są one równe na poziomie wartości. Jednak to od konkretnego typu zależy określenie Equalssposobu implementacji metody. Iteracyjne porównanie atrybutów, które mają prymitywne wartości, może nie wystarczyć, mogą istnieć atrybuty, których nie należy uważać za część wartości obiektu. Na przykład,

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

W powyższym przypadku cnie jest naprawdę ważne ustalenie, czy jakieś dwa wystąpienia MyClass są równe, tylko ai bsą ważne. W niektórych przypadkach cmogą się różnić między instancjami, ale nie są znaczące podczas porównania.

Uwaga: ta kwestia ma zastosowanie, gdy członkowie mogą być również instancjami danego typu i każdy z nich musiałby mieć środki do ustalenia równości.

Jeszcze bardziej skomplikowane jest to, że w JavaScript rozróżnia się rozróżnienie między danymi a metodą.

Obiekt może odwoływać się do metody, która ma zostać wywołana jako moduł obsługi zdarzeń, i prawdopodobnie nie będzie to uważane za część jego „stanu wartości”. Podczas gdy do innego obiektu można również przypisać funkcję, która wykonuje ważne obliczenia, a tym samym odróżnia ten przypadek od innych tylko dlatego, że odwołuje się do innej funkcji.

Co z obiektem, który ma jedną z istniejących metod prototypowych zastąpioną przez inną funkcję? Czy nadal można uznać to za równe innej instancji, która w przeciwnym razie byłaby identyczna? Na to pytanie można odpowiedzieć tylko w każdym konkretnym przypadku dla każdego rodzaju.

Jak wspomniano wcześniej, wyjątkiem byłby obiekt całkowicie bez typu. W takim przypadku jedynym rozsądnym wyborem jest iteracyjne i rekurencyjne porównanie każdego członka. Nawet wtedy trzeba zapytać, jaka jest „wartość” funkcji?


175
Jeśli używasz podkreślenia, możesz to zrobić_.isEqual(obj1, obj2);
chovy

12
@Harsh, odpowiedź nie dała żadnego rozwiązania, ponieważ nie ma żadnego. Nawet w Javie nie ma srebrnego punktu do porównania równości obiektów, a prawidłowe wdrożenie tej .equalsmetody nie jest trywialne, dlatego w Effective Java istnieje taki temat .
lcn

3
@Kumar Harsh, To, co wyrównuje dwa obiekty, jest bardzo specyficzne dla aplikacji; nie zawsze należy brać pod uwagę każdą właściwość obiektu, więc brutalne wymuszanie każdej właściwości obiektu również nie jest konkretnym rozwiązaniem.
sethro

googled javascript equality object, dostałem odpowiedź tl; dr, wziął jeden wiersz z komentarza @chovy. dziękuję
Andrea,

Jeśli używasz kątowego, maszangular.equals
Boatcoder

506

Po co wymyślać koło ponownie? Wypróbuj Lodash . Ma wiele niezbędnych funkcji, takich jak isEqual () .

_.isEqual(object, other);

Brutalna siła sprawdzi każdą kluczową wartość - podobnie jak inne przykłady na tej stronie - używając ECMAScript 5 i natywnych optymalizacji, jeśli są one dostępne w przeglądarce.

Uwaga: poprzednio ta odpowiedź zalecała Underscore.js , ale lodash lepiej radził sobie z naprawianiem błędów i spójnym rozwiązywaniem problemów.


27
Funkcja isEqual podkreślenia jest bardzo przyjemna (ale musisz pobrać jej bibliotekę, aby z niej skorzystać - około 3 KB spakowanych).
mckoss

29
jeśli spojrzysz na to, co jeszcze daje ci podkreślenie, nie pożałujesz, że to przyciągasz
PandaWood,

6
Nawet jeśli nie możesz sobie pozwolić na podkreślenie jako zależność, wyciągnij funkcję isEqual, spełnij wymagania licencyjne i przejdź dalej. To zdecydowanie najbardziej wszechstronny test równości wspomniany przy przepływie stosów.
Dale Anderson,

7
Istnieje rozwidlenie Underscore o nazwie LoDash, a ten autor jest bardzo zainteresowany takimi problemami ze spójnością. Przetestuj za pomocą LoDash i zobacz, co otrzymujesz.
CoolAJ86,

6
@mckoss możesz użyć samodzielnego modułu, jeśli nie chcesz całej biblioteki npmjs.com/package/lodash.isequal
Rob Fox

161

Domyślny operator równości w JavaScript dla Objects daje true, gdy odnoszą się do tej samej lokalizacji w pamięci.

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

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

Jeśli potrzebujesz innego operatora równości, musisz dodać equals(other)metodę lub coś podobnego do swoich klas, a specyfika domeny problemu określi, co to dokładnie oznacza.

Oto przykład karty do gry:

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

Jeśli obiekty można przekonwertować na ciąg JSON, upraszcza to funkcję equals ().
Scott

3
@scotts Nie zawsze. Konwertowanie obiektów na JSON i porównywanie ciągów może stać się intensywne obliczeniowo dla złożonych obiektów w ciasnych pętlach. W przypadku prostych obiektów prawdopodobnie nie ma to większego znaczenia, ale w rzeczywistości zależy to od konkretnej sytuacji. Prawidłowe rozwiązanie może być tak proste, jak porównanie identyfikatorów obiektów lub sprawdzenie każdej właściwości, ale jej poprawność podyktowana jest całkowicie przez domenę problemową.
Daniel X Moore

Czy nie powinniśmy również porównywać typu danych ?! return other.rank === this.rank && other.suit === this.suit;
devsathish

1
@devsathish prawdopodobnie nie. W JavaScript typy są dość szybkie i luźne, ale jeśli w twojej domenie typy są ważne, możesz również chcieć sprawdzić typy.
Daniel X Moore

7
@ scotts Innym problemem związanym z konwersją do JSON jest to, że kolejność właściwości w łańcuchu staje się znacząca. {x:1, y:2}! =={y:2, x:1}
Stijn de Witt

81

Jeśli pracujesz w AngularJS , angular.equalsfunkcja określi, czy dwa obiekty są równe. W Ember.js użyj isEqual.

  • angular.equals- Zobacz dokumentację lub źródło, aby uzyskać więcej informacji na temat tej metody. Dokładnie porównuje również tablice.
  • Ember.js isEqual- więcej informacji na temat tej metody można znaleźć w dokumentacji lub źródle . Nie dokonuje głębokiego porównania tablic.

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

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


66

To jest moja wersja. Korzysta z nowej funkcji Object.keys, która została wprowadzona w ES5, oraz pomysłów / testów z + , + i + :

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

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

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

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

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


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

assert.isTrue(objectEquals("hi","hi"));
assert.isTrue(objectEquals(5,5));
assert.isFalse(objectEquals(5,10));

assert.isTrue(objectEquals([],[]));
assert.isTrue(objectEquals([1,2],[1,2]));
assert.isFalse(objectEquals([1,2],[2,1]));
assert.isFalse(objectEquals([1,2],[1,2,3]));

assert.isTrue(objectEquals({},{}));
assert.isTrue(objectEquals({a:1,b:2},{a:1,b:2}));
assert.isTrue(objectEquals({a:1,b:2},{b:2,a:1}));
assert.isFalse(objectEquals({a:1,b:2},{a:1,b:3}));

assert.isTrue(objectEquals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}},{1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}));
assert.isFalse(objectEquals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}},{1:{name:"mhc",age:28}, 2:{name:"arb",age:27}}));

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

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

assertTrue("hi".equals("hi"));
assertTrue(new Number(5).equals(5));
assertFalse(new Number(5).equals(10));
assertFalse(new Number(1).equals("1"));

assertTrue([].equals([]));
assertTrue([1,2].equals([1,2]));
assertFalse([1,2].equals([2,1]));
assertFalse([1,2].equals([1,2,3]));
assertTrue(new Date("2011-03-31").equals(new Date("2011-03-31")));
assertFalse(new Date("2011-03-31").equals(new Date("1970-01-01")));

assertTrue({}.equals({}));
assertTrue({a:1,b:2}.equals({a:1,b:2}));
assertTrue({a:1,b:2}.equals({b:2,a:1}));
assertFalse({a:1,b:2}.equals({a:1,b:3}));

assertTrue({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}.equals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}));
assertFalse({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}.equals({1:{name:"mhc",age:28}, 2:{name:"arb",age:27}}));

var a = {a: 'text', b:[0,1]};
var b = {a: 'text', b:[0,1]};
var c = {a: 'text', b: 0};
var d = {a: 'text', b: false};
var e = {a: 'text', b:[1,0]};
var i = {
    a: 'text',
    c: {
        b: [1, 0]
    }
};
var j = {
    a: 'text',
    c: {
        b: [1, 0]
    }
};
var k = {a: 'text', b: null};
var l = {a: 'text', b: undefined};

assertTrue(a.equals(b));
assertFalse(a.equals(c));
assertFalse(c.equals(d));
assertFalse(a.equals(e));
assertTrue(i.equals(j));
assertFalse(d.equals(k));
assertFalse(k.equals(l));

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

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


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

objectEquals([1,2,3],{0:1,1:2,2:3})zwraca również true- np. nie ma sprawdzania typu, tylko sprawdzanie klucza / wartości.
Roy Tinker

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

1
if (x.constructor! == y.constructor) {return false; } To się zepsuje podczas porównywania dwóch „nowych ciągów („ a ”)” w różnych oknach. Aby uzyskać równość wartości, musisz sprawdzić, czy String.isString na obu obiektach, a następnie użyć luźnej kontroli równości „a == b”.
Triynko,

1
Istnieje ogromna różnica między równością „wartościową” a „ścisłą” równością i nie należy ich wdrażać w ten sam sposób. Równość wartości nie powinna przejmować się typami, oprócz podstawowej struktury, która jest jednym z tych 4: „obiekt” (tj. Zbiór par klucz / wartość), „liczba”, „ciąg” lub „tablica”. Otóż ​​to. Wszystko, co nie jest liczbą, łańcuchem ani tablicą, powinno być porównywane jako zestaw par klucz / wartość, niezależnie od tego, jaki jest konstruktor (bezpieczny dla wielu okien). Porównując obiekty, zrównaj wartość literałów i wystąpień liczby, ale nie zmuszaj ciągów znaków do liczb.
Triynko

50

Jeśli używasz biblioteki JSON, możesz zakodować każdy obiekt jako JSON, a następnie porównać otrzymane ciągi pod kątem równości.

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

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

UWAGA: Chociaż odpowiedź ta zadziała w wielu przypadkach, jak zauważyło kilka osób w komentarzach, jest ona problematyczna z różnych powodów. W większości przypadków będziesz chciał znaleźć bardziej niezawodne rozwiązanie.


91
Ciekawe, ale moim zdaniem trochę trudne. Na przykład, czy możesz w 100% zagwarantować, że właściwości obiektu będą zawsze generowane w tej samej kolejności?
Guido

25
To dobre pytanie i nasuwa kolejne pytanie, czy dwa obiekty o tych samych właściwościach w różnych rzędach są naprawdę równe, czy nie. Chyba zależy od tego, co rozumiesz przez pojęcie równości.
Joel Anair

11
Zauważ, że większość koderów i nawijaczy ignoruje funkcje i zamienia nieskończone liczby, takie jak NaN, na null.
Stephen Belanger

4
Zgadzam się z Guido, kolejność nieruchomości jest ważna i nie można tego zagwarantować. @JoelAnair, myślę, że dwa obiekty o tych samych właściwościach w różnych rzędach powinny być uważane za równe, jeśli wartość właściwości jest równa.
Juzer Ali

5
Może to działać z alternatywnym struningiem JSON, który konsekwentnie sortuje klucze obiektowe.
Roy Tinker

40

Krótkie deepEqualwdrożenie funkcjonalne :

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

Edycja : wersja 2, używając sugestii wysięgnika i funkcji strzałek ES6:

function deepEqual(x, y) {
  const ok = Object.keys, tx = typeof x, ty = typeof y;
  return x && y && tx === 'object' && tx === ty ? (
    ok(x).length === ok(y).length &&
      ok(x).every(key => deepEqual(x[key], y[key]))
  ) : (x === y);
}

5
Można wymienić reducez everyuprościć.
wysięg

1
@nonkertompf pewny on mógł: Object.keys(x).every(key => deepEqual(x[key], y[key])).
wysięgnik

2
Nie udaje się to, gdy porównujesz dwie daty
Greg

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

3
Tak, jeśli dbasz o takim przypadku narożnego, brzydka rozwiązaniem jest zastąpienie : (x === y)z: (x === y && (x != null && y != null || x.constructor === y.constructor))
atmin

22

Czy próbujesz sprawdzić, czy dwa obiekty są równe? tj .: ich właściwości są równe?

W takim przypadku prawdopodobnie zauważysz tę sytuację:

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

być może będziesz musiał zrobić coś takiego:

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

Oczywiście ta funkcja mogłaby przydać się z dość dużą optymalizacją i możliwością głębokiego sprawdzania (do obsługi zagnieżdżonych obiektów: var a = { foo : { fu : "bar" } } ale masz pomysł.

Jak wskazano na FOR, być może będziesz musiał dostosować to do własnych celów, np .: różne klasy mogą mieć różne definicje „równości”. Jeśli pracujesz tylko z prostymi obiektami, powyższe może wystarczyć, w przeciwnym razie MyClass.equals()możesz skorzystać z funkcji niestandardowej .


Jest to długa metoda, ale całkowicie testuje obiekty, nie przyjmując żadnych założeń co do kolejności właściwości w każdym obiekcie.
briancollins081

22

Jeśli masz pod ręką funkcję głębokiego kopiowania, możesz skorzystać z następującej sztuczki, aby nadal używać JSON.stringify, dopasowując kolejność właściwości:

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/

Racjonalne uzasadnienie:

Ponieważ właściwości obj1są kopiowane do klonu jeden po drugim, ich kolejność w klonie zostanie zachowana. A kiedy właściwości obj2zostaną skopiowane do klonu, ponieważ właściwości już istniejące w obj1zostaną po prostu nadpisane, ich zamówienia w klonie zostaną zachowane.


11
Nie sądzę, że zachowanie kolejności jest gwarantowane w przeglądarkach / silnikach.
Jo Liss,

@ Potrzebne cytaty JoLiss;) Pamiętam, że testowałem to w wielu przeglądarkach, uzyskując spójne wyniki. Ale oczywiście nikt nie może zagwarantować, że zachowanie pozostanie takie samo w przyszłych przeglądarkach / silnikach. Jest to w najlepszym razie sztuczka (jak już wspomniano w odpowiedzi) i nie miałem na myśli tego, by być pewnym sposobem na porównywanie obiektów.
Ates Goral

1
Jasne, oto kilka wskazówek: specyfikacja ECMAScript mówi, że obiekt jest „nieuporządkowany” ; i ta odpowiedź na rzeczywiste rozbieżne zachowania w obecnych przeglądarkach.
Jo Liss,

2
@JoLiss Dzięki za to! Pamiętaj jednak, że nigdy nie domagałem się zachowania porządku między kodem a kompilowanym obiektem. Domagałem się zachowania kolejności nieruchomości, których wartości są zastępowane na miejscu. To był klucz do mojego rozwiązania: użyć mixinu, aby po prostu zastąpić wartości właściwości. Zakładając, że implementacje zazwyczaj używają pewnego rodzaju haszowania, zastąpienie tylko wartości powinno zachować porządek kluczy. To właśnie dokładnie przetestowałem w różnych przeglądarkach.
Ates Goral

1
@AtesGoral: czy można uczynić to ograniczenie nieco bardziej wyraźnym (pogrubienie, ...). Większość ludzi po prostu kopiuje i wkleja, nie czytając otaczającego go tekstu ...
Willem Van Onsem,

21

W Node.js możesz użyć jego natywnego require("assert").deepStrictEqual. Więcej informacji: http://nodejs.org/api/assert.html

Na przykład:

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

Kolejny przykład, który zwraca true/ falsezamiast zwraca błędy:

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

Chaima również tę funkcję. W takim przypadku użyjesz:var foo = { a: 1 }; var bar = { a: 1 }; expect(foo).to.deep.equal(bar); // true;
Folusho Oladipo

Niektóre wersje Node.js ustawione error.namena "AssertionError [ERR_ASSERTION]". W takim przypadku zastąpiłbym instrukcję if if (error.code === 'ERR_ASSERTION') {.
Knute Knudsen,

Nie miałem pojęcia, deepStrictEqualjak pójść. Dręczyłem mózg, próbując dowiedzieć się, dlaczego strictEqualnie działa. Fantastyczny.
NetOperator Wibby

19

Najprostsze i logiczne rozwiązania do porównywania wszystkiego jak Object, Array, String, Int ...

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

Uwaga:

  • musisz wymienić val1iz val2Twoim Obiektem
  • dla obiektu musisz sortować (według klucza) rekurencyjnie dla obu obiektów bocznych

6
Zakładam, że w wielu przypadkach to nie zadziała, ponieważ kolejność kluczy w obiektach nie ma znaczenia - chyba JSON.stringifyże zmiana kolejności alfabetycznej? (Których nie mogę znaleźć udokumentowane .)
Bram Vanroy

tak, masz rację ... dla obiektu musisz sortować rekurencyjnie dla obu bocznych obiektów
Pratik Bhalodiya,

2
To nie działa dla obiektów z referencjami cyklicznymi
Nate-Bit Int

13

Używam tej comparablefunkcji do tworzenia kopii moich obiektów, które są porównywalne z 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>

Przydaje się w testach (większość frameworków testowych ma swoją isfunkcję). Na przykład

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

W przypadku wychwycenia różnicy zapisywane są ciągi, dzięki czemu różnice są widoczne:

x must match y
got      {"a":1,"b":{"0":2,"1":3},"c":7,"d":{"e":"5","f":null}},
expected {"a":1,"b":{"0":2,"1":3},"c":4,"d":{"e":"5","f":null}}.

1
dobry pomysł (w moim przypadku obiekty do porównania to tylko para klucz / wartość, bez specjalnych rzeczy)
mech

10

Oto rozwiązanie w ES6 / ES2015 wykorzystujące podejście funkcjonalne:

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

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

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

demo dostępne tutaj


Nie działa, jeśli zmieniła się kolejność kluczy obiektów.
Isaac Pak

9

Nie wiem, czy ktoś opublikował coś podobnego do tego, ale oto funkcja, którą sprawdziłem pod kątem równości obiektów.

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

Ponadto jest rekurencyjny, więc może również sprawdzać głęboką równość, jeśli tak to nazywacie.


mała korekta: przed przejściem przez każdą rekwizytę aib dodaj tę kontrolę, jeśli (Object.getOwnPropertyNames (a) .length! == Object.getOwnPropertyNames (b) .length) zwraca false
Hith

1
oczywiste jest, że właściwy kontroler równości musi być rekurencyjny. Myślę, że jedna z takich rekurencyjnych odpowiedzi powinna być poprawną odpowiedzią. Przyjęta odpowiedź nie podaje kodu i nie pomaga
canbax 17.01.19

9

Dla tych, którzy używają NodeJS, istnieje wygodna metoda wywoływana isDeepStrictEqualw natywnej bibliotece Util, która może to osiągnąć.

const util = require('util');

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

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


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

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


jego wydajność ma być dobra. Bez obaw. Nawet tego używam w złożonych scenariuszach. Ponieważ, kiedy z tego korzystamy, nie musimy się martwić, czy właściwość obiektu ma obiekt lub tablicę. Json.Stringify sprawia, że ​​i tak jest to ciąg znaków, a porównywanie ciągów w javascript nie jest wielkim problemem
TrickOrTreat

6

ES6: Oto minimalny kod, który mógłbym zrobić. Dokonuje głębokiego porównania rekurencyjnie poprzez stritowanie wszystkich obiektów, jedynym ograniczeniem jest brak metod lub porównywanie symboli.

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

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


To jest w pełni funkcjonalna odpowiedź, dzięki @Adriano Spadoni. Czy wiesz, jak mogę uzyskać klucz / atrybut, który został zmodyfikowany? Dzięki,
digitai

1
Cześć @ Digital, jeśli potrzebujesz różnych klawiszy, nie jest to idealna funkcja. Sprawdź drugą odpowiedź i użyj jednej z pętlą przez obiekty.
Adriano Spadoni

5

możesz użyć _.isEqual(obj1, obj2)z biblioteki underscore.js.

Oto przykład:

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

Zobacz oficjalną dokumentację tutaj: http://underscorejs.org/#isEqual


5

Zakładając, że kolejność właściwości w obiekcie nie ulega zmianie.

JSON.stringify () działa na głębokie i nie głębokie oba typy obiektów, niezbyt pewny aspektów wydajnościowych:

var object1 = {
  key: "value"
};

var object2 = {
  key: "value"
};

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

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

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


1
To nie robi tego, czego chce OP, ponieważ będzie pasować tylko wtedy, gdy oba obiekty będą miały wszystkie te same klucze, których, jak twierdzą, nie będą. Wymagałoby to również, aby klucze były w tej samej kolejności, co również nie jest rozsądne.
SpeedOfRound

1
Jakie są właściwości w innej kolejności? Nie jest to dobra metoda
Vishal Sakaria

4

Prostym rozwiązaniem tego problemu, którego wiele osób nie zdaje sobie sprawy, jest sortowanie ciągów JSON (według znaków). Jest to również zwykle szybsze niż w przypadku innych wymienionych tutaj rozwiązań:

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

Kolejną przydatną rzeczą w tej metodzie jest to, że możesz filtrować porównania, przekazując funkcję „zamiennika” do funkcji JSON.stringify ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON / stringify # Example_of_using_replacer_parameter ). Poniższe porównuje tylko wszystkie klucze obiektów o nazwie „derp”:

function areEqual(obj1, obj2, filter) {
    var a = JSON.stringify(obj1, filter), b = JSON.stringify(obj2, filter);
    if (!a) a = '';
    if (!b) b = '';
    return (a.split('').sort().join('') == b.split('').sort().join(''));
}
var equal = areEqual(obj1, obj2, function(key, value) {
    return (key === 'derp') ? value : undefined;
});

1
Och, również zapomniałem, ale funkcję można przyspieszyć, najpierw testując obiekt równy i ratując wcześniej, jeśli są one tym samym obiektem: if (obj1 === obj2) return true;
th317erd

11
areEqual({a: 'b'}, {b: 'a'})dostaje trueto?
okm,

Tak, zdałem sobie sprawę po opublikowaniu, że to „rozwiązanie” ma problemy. Potrzebuje nieco więcej pracy w algorytmie sortowania, aby faktycznie działał poprawnie.
th317erd

4

Chciałem tylko dodać moją wersję porównania obiektów wykorzystującą niektóre funkcje es6. Nie uwzględnia zamówienia. Po konwersji wszystkich if / else na trójkę przyszedłem z następującymi elementami:

function areEqual(obj1, obj2) {

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

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

        }
    )
}

3

Potrzebując bardziej ogólnej funkcji porównywania obiektów niż wcześniej napisałem, przygotowałem następujące. Doceniona krytyka ...

Object.prototype.equals = function(iObj) {
  if (this.constructor !== iObj.constructor)
    return false;
  var aMemberCount = 0;
  for (var a in this) {
    if (!this.hasOwnProperty(a))
      continue;
    if (typeof this[a] === 'object' && typeof iObj[a] === 'object' ? !this[a].equals(iObj[a]) : this[a] !== iObj[a])
      return false;
    ++aMemberCount;
  }
  for (var a in iObj)
    if (iObj.hasOwnProperty(a))
      --aMemberCount;
  return aMemberCount ? false : true;
}

2
Skończyło się na tym wariancie. Dzięki za pomysł liczenia członków!
NateS

2
Bądź bardzo ostrożny przy modyfikowaniu Object.prototype- w zdecydowanej większości przypadków nie jest to zalecane (dodatki pojawiają się na przykład we wszystkich pętlach for..in). Może rozważyć Object.equals = function(aObj, bObj) {...}?
Roy Tinker

3

Jeśli porównujesz obiekty JSON, możesz użyć https://github.com/mirek/node-rus-diff

npm install rus-diff

Stosowanie:

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

Jeśli dwa obiekty są różne, {$rename:{...}, $unset:{...}, $set:{...}}zwracany jest obiekt podobny do MongoDB .


3

Napotkałem ten sam problem i postanowiłem napisać własne rozwiązanie. Ale ponieważ chcę również porównywać tablice z obiektami i odwrotnie, stworzyłem ogólne rozwiązanie. Postanowiłem dodać funkcje do prototypu, ale można łatwo przepisać je do samodzielnych funkcji. Oto kod:

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

Ten algorytm jest podzielony na dwie części; Sama funkcja równa się i funkcja znajdująca indeks numeryczny właściwości w tablicy / obiekcie. Funkcja wyszukiwania jest potrzebna tylko dlatego, że indexof znajduje tylko liczby i ciągi znaków, a nie żadnych obiektów.

Można to tak nazwać:

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

Funkcja zwraca true lub false, w tym przypadku true. Algorytm pozwala również na porównanie bardzo złożonych obiektów:

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

Górny przykład zwróci wartość true, nawet jeśli właściwości mają inną kolejność. Jeden mały szczegół, na który należy zwrócić uwagę: ten kod sprawdza również ten sam typ dwóch zmiennych, więc „3” to nie to samo co 3.


3

Widzę odpowiedzi na kod spaghetti. Bez korzystania z bibliotek stron trzecich jest to bardzo łatwe.

Najpierw posortuj dwa obiekty według klucza ich nazw kluczy.

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)

Następnie po prostu użyj ciągu znaków, aby je porównać.

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

To również nie działa w przypadku głębokiego kopiowania, ma tylko jedną głębokość iteracji.
andras

Myślę, że @andras oznacza, że ​​musisz rekurencyjnie sortować klucze zagnieżdżonych obiektów.
Davi Lima,

2

Odradzam mieszanie lub serializację (jak sugerują rozwiązania JSON). Jeśli musisz sprawdzić, czy dwa obiekty są równe, musisz zdefiniować, co oznacza równość. Może się zdarzyć, że wszystkie elementy danych w obu obiektach będą pasować, lub może być tak, że lokalizacje pamięci muszą się zgadzać (co oznacza, że ​​obie zmienne odnoszą się do tego samego obiektu w pamięci), lub może być tak, że tylko jeden element danych w każdym obiekcie musi pasować.

Ostatnio opracowałem obiekt, którego konstruktor tworzy nowy identyfikator (zaczynając od 1 i zwiększając o 1) za każdym razem, gdy tworzona jest instancja. Ten obiekt ma funkcję isEqual, która porównuje tę wartość id z wartością id innego obiektu i zwraca wartość true, jeśli są one zgodne.

W takim przypadku zdefiniowałem „równy” jako oznaczający, że wartości id są zgodne. Biorąc pod uwagę, że każda instancja ma unikalny identyfikator, można to wykorzystać do wymuszenia przekonania, że ​​pasujące obiekty również zajmują to samo miejsce w pamięci. Chociaż nie jest to konieczne.


2

Przydatne jest uznanie dwóch obiektów za równe, jeśli mają wszystkie takie same wartości dla wszystkich właściwości i rekurencyjnie dla wszystkich zagnieżdżonych obiektów i tablic. Uważam również następujące dwa obiekty za równe:

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

Podobnie tablice mogą mieć „brakujące” elementy i niezdefiniowane elementy. Traktowałbym je również tak samo:

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

Funkcja, która implementuje tę definicję równości:

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

Kod źródłowy (w tym funkcje pomocnicze, generalType i uniqueArray): Test jednostkowy i Test Runner tutaj .


2

Za pomocą tej funkcji przyjmuję następujące założenia:

  1. Kontrolujesz porównywane obiekty i masz tylko prymitywne wartości (tj. Nie zagnieżdżone obiekty, funkcje itp.).
  2. Twoja przeglądarka obsługuje Object.keys .

Należy to potraktować jako demonstrację prostej strategii.

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

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

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

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

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

    return true;
}

2

Jest to dodatek do wszystkich powyższych, a nie zamiennik. Jeśli chcesz szybko porównać obiekty bez konieczności sprawdzania dodatkowych przypadków rekurencyjnych. Oto strzał.

Dla porównania: 1) Równość liczby własnych właściwości, 2) Równość nazw kluczy, 3) jeśli bCompareValues ​​== true, Równość odpowiadających wartości właściwości i ich typów (potrójna równość)

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

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

2

Do porównywania kluczy dla instancji obiektów prostych par klucz / wartość używam:

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

Po porównaniu kluczy wystarczy zwykła dodatkowa for..inpętla.

Złożoność to O (N * N), a N to liczba kluczy.

Mam nadzieję, że obiekty, które zdefiniuję, nie będą zawierać więcej niż 1000 właściwości ...


2

Wiem, że to trochę stare, ale chciałbym dodać rozwiązanie, które wymyśliłem dla tego problemu. Miałem obiekt i chciałem wiedzieć, kiedy zmieniły się jego dane. „coś podobnego do Object.observe” i to, co zrobiłem, to:

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

Można to tutaj powielić i stworzyć inny zestaw tablic, aby porównać wartości i klucze. Jest to bardzo proste, ponieważ są teraz tablicami i zwrócą wartość false, jeśli obiekty mają różne rozmiary.


1
To zawsze zwróci false, ponieważ tablice nie porównują wartości, np [1,2] != [1,2].
wysięg
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.