Як визначити рівність для двох об’єктів JavaScript?


669

Оператор суворої рівності скаже вам, якщо два типи об'єктів рівні. Однак чи існує спосіб визначити, чи два об'єкти рівні, як значення хеш-коду на Java?

Питання про переповнення стека Чи є в JavaScript функція hashCode? подібне до цього питання, але вимагає більш академічної відповіді. Наведений вище сценарій демонструє, чому це було б потрібно, і мені цікаво, чи є рівнозначне рішення .


3
Також погляньте на це питання stackoverflow.com/q/1068834/1671639
Praveen

37
Слід зазначити, що навіть в Java, a.hashCode() == b.hashCode()зовсім НЕ означає , що aтак само b. Це необхідна умова, а не достатня.
Хайнзі

Pls поглянути в цей QS: stackoverflow.com/questions/35933616 / ...
forgottofly

2
Якщо ви хотіли порівнювати об'єкти у своєму коді, ніж ви, ймовірно, неправильно пишете свій код. Питання може бути краще: "Як я можу написати цей код, щоб мені не довелося порівнювати об'єкти?"
th317erd

3
@ th317erd, будь ласка, поясніть себе? ...
El Mac

Відповіді:


175

Коротка відповідь

Проста відповідь: Ні, немає загальних засобів, щоб визначити, що об'єкт дорівнює іншому в тому сенсі, який ви маєте на увазі. Виняток - коли ви суворо думаєте про те, що об'єкт є безтиповим.

Довга відповідь

Концепція - це метод рівняння, який порівнює два різних екземпляри об'єкта, щоб вказати, чи рівні вони на рівні значень. Однак визначити, як Equalsметод слід реалізувати , залежить від конкретного типу . Ітераційне порівняння атрибутів, які мають примітивні значення, може бути недостатньою, можливо, можуть бути атрибути, які не слід вважати частиною об'єктного значення. Наприклад,

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

У цьому вище випадку cне дуже важливо визначати, чи будь-які два екземпляри MyClass рівні, лише aі bє важливими. У деяких випадках cможе відрізнятися між окремими прикладами, але ще не мати суттєвого значення для порівняння.

Зауважте, ця проблема застосовується, коли самі члени також можуть бути типовими екземплярами, і всі вони повинні мати засоби визначення рівності.

Подальше ускладнює те, що в JavaScript різниця між даними та методом розмита.

Об'єкт може посилатися на метод, який повинен називатися обробником подій, і це, швидше за все, не вважатиметься частиною його «стану значень». Тоді як іншому об'єкту цілком може бути призначена функція, яка виконує важливий обчислення, і тим самим робить цей екземпляр відмінним від інших просто тому, що він посилається на іншу функцію.

А що з об’єктом, який має один із існуючих методів прототипу, замінений на іншу функцію? Чи все-таки можна вважати рівним інший екземпляр, який інакше ідентичний? На це питання можна відповісти лише в кожному конкретному випадку для кожного типу.

Як було сказано раніше, виняток становитиме суто безтиповий об'єкт. У цьому випадку єдиним розумним вибором є ітеративне та рекурсивне порівняння кожного члена. Вже тоді потрібно запитати, яке «значення» функції?


175
Якщо ви використовуєте підкреслення, ви можете просто зробити_.isEqual(obj1, obj2);
chovy

12
@Harsh, відповідь не змогла дати жодного рішення, оскільки його немає. Навіть у Java немає срібної кулі, яка б заперечувала порівняння рівності та правильно реалізовувала .equalsметод, не є тривіальною, саме тому існує така тема, присвячена в Ефективній Java .
lcn

3
@Kumar Harsh, Що робить два об'єкти рівними, це дуже специфічне застосування; не кожне властивість об'єкта повинно бути обов'язково враховано, тому жорстоке насильство над кожною властивістю об'єкта також не є конкретним рішенням.
sethro

googled javascript equality object, отримав tl; доктор відповідь, взяв один-лайнер з коментаря @chovy. дякую
Андреа

Якщо ви використовуєте кутовий, ви маєтеangular.equals
boatcoder

506

Навіщо винаходити колесо? Спробуйте Лодаш . Він має ряд обов'язкових функцій, таких як isEqual () .

_.isEqual(object, other);

Це змусить перевірити кожне ключове значення - як і інші приклади на цій сторінці - використовуючи ECMAScript 5 та вбудовані оптимізації, якщо вони доступні у веб-переглядачі.

Примітка: Раніше ця відповідь рекомендувала Underscore.js , але lodash зробив кращу роботу з виправлення помилок та вирішення проблем із послідовністю.


27
Функція підкреслення isEqual дуже приємна (але для її використання вам потрібно зайняти їхню бібліотеку - близько 3 КГ).
mckoss

29
якщо ви подивитеся на те, що ще дає підкреслення, ви не пошкодуєте, що
втягнули

6
Навіть якщо ви не можете дозволити собі підкреслення як залежність, витягніть функцію isEqual, задовольнити ліцензійні вимоги та продовжуй рухатися далі. Це, безумовно, найбільш всебічний тест рівності, згаданий у стартовому потоці.
Дейл Андерсон

7
Існує роздвоєння підкреслення під назвою LoDash, і цей автор дуже стурбований такими питаннями послідовності. Перевірте LoDash і подивіться, що ви отримаєте.
CoolAJ86

6
@mckoss ви можете використовувати окремий модуль, якщо вам не потрібна вся бібліотека npmjs.com/package/lodash.isequal
Роб Фокс

161

Оператор рівності за замовчуванням в JavaScript для об’єктів дає істину, коли вони посилаються на одне і те ж місце в пам'яті.

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

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

Якщо вам потрібен інший оператор рівності, вам потрібно буде додати equals(other)метод або щось подібне до своїх класів, а специфіка вашого проблемного домену визначатиме, що саме це означає.

Ось приклад ігрової карти:

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

Якщо об'єкти (и) можуть бути перетворені в рядок JSON, це робить функцію equals () простою.
шотландці

3
@scotts Не завжди. Перетворення об'єктів у JSON та порівняння рядків можуть стати обчислювально інтенсивними для складних об'єктів у вузьких петлях. Для простих об'єктів це, мабуть, не має великого значення, але насправді це справді залежить від вашої конкретної ситуації. Правильне рішення може бути таким же простим, як порівняння ідентифікаторів об’єкта або перевірка кожного властивості, але його правильність повністю продиктована проблемною областю.
Даніель X Мур

Чи не слід також порівнювати тип даних ?! повернути other.rank === this.rank && other.suit === this.suit;
devsathish

1
@devsathish, мабуть, ні. У JavaScript типи досить швидкі та вільні, але якщо у вашому домі важливі типи, тоді ви також можете перевірити типи.
Даніель Х Мур

7
@scotts Інша проблема перетворення в JSON полягає в тому, що порядок властивостей у рядку стає значним. {x:1, y:2}! =={y:2, x:1}
Штійн де Вітт

81

Якщо ви працюєте в AngularJS , angular.equalsфункція визначатиме, чи два об'єкти рівні. У Ember.js використання isEqual.

  • angular.equals- Докладніше про цей метод див. У документах або джерелах . Це також глибоке порівняння на масивах.
  • Ember.js isEqual- Докладніше про цей метод див. У документах або джерелах . Це не робить глибокого порівняння на масивах.

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

Це моя версія. Він використовує нову функцію Object.keys, яка вводиться в ES5 та ідеї / тести з + , + та + :

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])поверненняtrue
Рой Тінкер

objectEquals([1,2,3],{0:1,1:2,2:3})також повертається true- наприклад, немає перевірки типу, лише перевірка ключа / значення.
Рой Тінкер

objectEquals(new Date(1234),1234)повертаєтьсяtrue
Рой Тінкер

1
if (x.constructor! == y.constructor) {return false; } Це порушиться при порівнянні двох "нових рядків (" a ")" в різних вікнах. Для рівності значень вам доведеться перевірити, чи є String.isString на обох об'єктах, а потім використати вільну рівність рівності 'a == b'.
Трайнко

1
Існує величезна різниця між рівністю «цінність» і «суворою» рівністю, і вони не повинні реалізовуватися однаково. Рівність значень не повинна піклуватися про типи, окрім основної структури, яка є одним із цих 4: "об'єкт" (тобто набір пар ключів / значень), "число", "рядок" або "масив". Це воно. Все, що не є числом, рядком чи масивом, має порівнюватися як набір пар ключів / значень, незалежно від того, що це конструктор (безпечний перехресний вікно). Порівнюючи об'єкти, прирівнюйте значення буквальних чисел та екземплярів Числа, але не примушуйте рядки до чисел.
Трайнко

50

Якщо ви використовуєте бібліотеку JSON, ви можете кодувати кожен об'єкт як JSON, а потім порівняти отримані рядки для рівності.

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

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

ПРИМІТКА. Хоча ця відповідь спрацює у багатьох випадках, як кілька людей зазначили у коментарях, вона є проблематичною з різних причин. В більшості випадків ви хочете знайти більш надійне рішення.


91
Цікаво, але трохи складно на мій погляд. Наприклад, чи можете ви на 100% гарантувати, що властивості об’єкта будуть генеруватися завжди в одному порядку?
Гвідо

25
Це хороше питання, і виникає інше, чи справді два об'єкти з однаковими властивостями в різних порядках дійсно рівні чи ні. Я думаю, залежить від того, що ви розумієте під рівнем.
Джоель Анаір

11
Зауважимо, що більшість кодерів і стрифіфікаторів ігнорують функції та перетворюють нескінченні числа, як-от NaN, в нуль.
Стівен Белангер

4
Я погоджуюся з Гвідо, порядок розміщення важливий, і це не може бути гарантовано. @JoelAnair, я думаю, що два об'єкти з однаковими властивостями в різних порядках слід вважати рівними, якщо значення властивостей рівні.
Юзер Алі

5
Це може працювати з альтернативним стрифіфікатором JSON, який послідовно сортує клавіші об’єктів.
Рой Тінкер

40

Коротка функціональна 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);
}

Редагувати : версія 2, використовуючи пропозицію jib та стрілки 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
Ви можете замінити reduceна, everyщоб спростити.
гусек

1
@nonkertompf впевнений , що він міг: Object.keys(x).every(key => deepEqual(x[key], y[key])).
липень

2
Це не вдається, коли ви порівнюєте дві дати
Грег,

3
deepEqual ({}, []) повертається правдою
AlexMorley-Finch

3
так, якщо ви піклуєтесь про такий кутовий випадок, некрасиве рішення - замінити : (x === y)на: (x === y && (x != null && y != null || x.constructor === y.constructor))
atmin

22

Ви намагаєтесь перевірити, чи два об'єкти рівні? тобто: їх властивості рівні?

Якщо це так, ви, ймовірно, помітили таку ситуацію:

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

можливо, вам доведеться зробити щось подібне:

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

Очевидно, що ця функція могла б зробити досить оптимізацією та можливістю глибокої перевірки (для обробки вкладених об'єктів:), var a = { foo : { fu : "bar" } }але ви отримаєте ідею.

Як зазначалося ДЛЯ, можливо, вам доведеться адаптувати це для власних цілей, наприклад: різні класи можуть мати різні визначення "рівних". Якщо ви просто працюєте з простими об'єктами, вищезазначеного може бути достатньо, інакше спеціальна MyClass.equals()функція може бути дорогою.


Це довгий метод, але він повністю перевіряє об'єкти, не роблячи припущень щодо порядку властивостей кожного об'єкта.
briancollins081

22

Якщо у вас є функція глибокої копіювання, ви можете використовувати наступний трюк, який все ще використовуватимуть, дотримуючись JSON.stringifyпорядку властивостей:

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

Демонстрація: http://jsfiddle.net/CU3vb/3/

Обгрунтування:

Оскільки властивості obj1атрибутуються в клон по черзі, їх порядок у клоні буде збережено. І коли властивості obj2копіюються в клон, оскільки властивості, які вже існують у obj1, просто будуть перезаписані, їхні порядки в клоні будуть збережені.


11
Я не думаю, що збереження замовлень гарантується в браузерах / двигунах.
Джо Лісс

@JoLiss Цитати потрібні;) Я пригадую тестування цього в декількох браузерах, отримуючи стійкі результати. Але звичайно, ніхто не може гарантувати, що поведінка залишається такою ж у майбутніх браузерах / двигунах. Це хитрість (як уже закликано у відповіді) у кращому випадку, і я не мав на увазі це надійний спосіб порівняння об'єктів.
Атес Горал

1
Звичайно, ось деякі вказівки: специфікація ECMAScript говорить, що об'єкт "не упорядкований" ; і це відповідь на фактичну розбіжність у поточних браузерах.
Джо Лісс

2
@JoLiss Дякую за це! Але зауважте, що я ніколи не вимагав збереження порядку між кодом та компільованим об'єктом. Я претендував на збереження порядку властивостей, значення яких замінюються на місці. Це було головним моїм рішенням: використовувати mixin, щоб просто перезаписати значення властивостей. Якщо припустити, що реалізація зазвичай вирішує використовувати якусь хеш-карту, заміна просто значень повинна зберігати порядок ключів. Насправді саме це я тестував у різних браузерах.
Атес Горал

1
@AtesGoral: чи можна зробити це обмеження трохи більш явним (жирний шрифт, ...). Більшість людей просто роблять копіювання-вставлення, не читаючи навколо нього текст ...
Віллем Ван Онсем

21

У Node.js ви можете використовувати його рідний require("assert").deepStrictEqual. Більше інформації: http://nodejs.org/api/assert.html

Наприклад:

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

Ще один приклад, який повертає true/ falseзамість повертає помилки:

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

Chaiє і ця особливість. У цьому випадку ви будете використовувати:var foo = { a: 1 }; var bar = { a: 1 }; expect(foo).to.deep.equal(bar); // true;
Folusho Oladipo

Деякі версії Node.js встановлені error.nameна "AssertionError [ERR_ASSERTION]". У цьому випадку я заміню оператор if на if (error.code === 'ERR_ASSERTION') {.
Кнут Кнудсен

Я поняття не deepStrictEqualмав, яким шляхом слід їхати. Я перекручував мозок, намагаючись зрозуміти, чому strictEqualне працює. Фантастичний.
NetOperator Wibby

19

Найпростіші та логічні рішення для порівняння всього, наприклад, об’єкта, масиву, рядків, внутрішніх ...

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

Примітка:

  • вам потрібно замінити val1і val2своїм об’єктом
  • для об'єкта потрібно сортувати (за ключем) рекурсивно для обох бічних об'єктів

6
Я припускаю, що це не спрацює у багатьох випадках, оскільки порядок клавіш в об'єктах не має значення - хіба JSON.stringifyалфавітне переупорядкування? (Що я не можу знайти документально .)
Брам Ванрой

так ви маєте рацію ... для об'єкта ви повинні сортувати рекурсивно для обох бічних об'єктів
Пратік Бхалодя

2
Це не працює для об’єктів із круговими посиланнями
Nate-Bit Int

13

Я використовую цю comparableфункцію для створення копій моїх об'єктів, порівняних з 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>

Корисний тест (більшість тестових рамок мають isфункцію). Напр

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

Якщо різниця виявлена, рядки реєструються, що робить відмінності помітними:

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
хороша ідея (у моєму випадку об’єкти, які потрібно порівнювати лише за допомогою клавіш / значень пар, немає спеціальних речей)
mech

10

Тут є рішення у ES6 / ES2015, використовуючи підхід у функціональному стилі:

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

демонстрація доступна тут


Не працює, якщо порядок дії об’єктних клавіш змінився.
Ісаак Пак

9

Я не знаю, чи хтось розмістив щось подібне до цього, але ось функція, яку я зробив для перевірки рівності об'єкта.

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

Крім того, він є рекурсивним, тому він також може перевірити наявність глибокої рівності, якщо це так ви називаєте.


невелика корекція: перед тим, як пройти кожен реквізит в a і b, додайте цей прапорець, якщо (Object.getOwnPropertyNames (a) .length! == Object.getOwnPropertyNames (b) .length) повернути помилково
Hith

1
очевидно, що належна перевірка рівності повинна бути рекурсивною. Я думаю, що одна з таких рекурсивних відповідей повинна бути правильною. Прийнята відповідь не дає коду і не допомагає
canbax

9

Для тих, хто використовує NodeJS, існує зручний метод, який називається isDeepStrictEqualу рідній бібліотеці Util, який може цього досягти.

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


її припущення, мабуть, хороші. Не хвилюйтесь. Навіть я використовую це також у складних сценаріях. Як і коли ми використовуємо це, нам не потрібно хвилюватися, чи властивість об'єкта несе об'єкт чи масив. Json.Stringify все одно робить його рядком і порівняння рядків у JavaScript не є великою справою
TrickOrTreat

6

ES6: Мінімальний код, який я міг би зробити, це такий. Це робиться глибоке порівняння рекурсивно шляхом строфікації всіх об'єктів, єдиним обмеженням є відсутність методів чи символів порівняння.

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

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


Це повністю функціональна відповідь, дякую @Adriano Spadoni. Чи знаєте ви, як я можу отримати ключ / атрибут, який був змінений? Дякую,
digitai

1
привіт @digital, якщо вам потрібні, які клавіші різні, це не ідеальна функція. Перевірте іншу відповідь і використовуйте одну з петлею через об’єкти.
Адріано Спадоні

5

ви можете використовувати _.isEqual(obj1, obj2)з бібліотеки underscore.js.

Ось приклад:

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

Дивіться офіційну документацію звідси: http://underscorejs.org/#isEqual


5

Якщо припустити, що порядок властивостей в об’єкті не змінений.

JSON.stringify () працює для глибоких і неглибоких обох типів об'єктів, не дуже впевнених у аспектах продуктивності:

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
Це не робить те, що хоче ОП, оскільки воно відповідатиме лише тому, що обидва об'єкти мають однакові клавіші, за якими вони заявляють, що не будуть. Також потрібно було б, щоб ключі були в тому ж порядку, що теж не дуже розумно.
SpeedOfRound

1
Які властивості в різному порядку ??? Ні, не хороший метод
Вішал

4

Просте рішення цього питання, яке багато хто не розуміє, - це сортування рядків JSON (за символом). Зазвичай це швидше, ніж інші рішення, згадані тут:

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

Ще одна корисна річ щодо цього методу - ви можете фільтрувати порівняння, передаючи функцію "замінювача" до функцій JSON.stringify ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON / stringify # Example_of_using_replacer_parameter ). Нижче наведено лише порівняння всіх клавіш об'єктів, названих "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
О, я також забув, але функцію можна пришвидшити, попередньо випробувавши об'єкт, що дорівнює та заздалегідь підкреслити, якщо вони однакові: якщо (obj1 === obj2) повернути true;
th317erd

11
areEqual({a: 'b'}, {b: 'a'})отримує trueтоді?
окм

Так, після публікації я зрозумів, що це "рішення" має проблеми. Щоб реально працювати належним чином, потрібно трохи більше попрацювати в алгоритмі сортування.
th317erd

4

Просто хотів зробити свій варіант порівняння об'єктів, використовуючи деякі функції es6. Він не враховує замовлення. Після перетворення всіх if / else в термінал я прийшов із наступним:

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

Потрібна більш загальна функція порівняння об'єктів, ніж була розміщена, я підготував наступне. Критика оцінена ...

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
Я в кінцевому підсумку використовував варіацію цього. Дякуємо за ідею підрахунку членів!
NateS

2
Будьте дуже обережні щодо Object.prototypeвнесення змін - у переважній більшості випадків це не радиться (доповнення з’являються у всіх, наприклад, петлях). Можливо, врахуйте Object.equals = function(aObj, bObj) {...}?
Рой Тінкер

3

Якщо ви порівнюєте об'єкти JSON, ви можете використовувати https://github.com/mirek/node-rus-diff

npm install rus-diff

Використання:

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

Якщо два об'єкти різні, {$rename:{...}, $unset:{...}, $set:{...}}повертається MongoDB, подібний об'єкту.


3

Я зіткнувся з тією ж проблемою і вирішив написати власне рішення. Але оскільки я хочу порівнювати масиви з об'єктами і навпаки, я створив загальне рішення. Я вирішив додати функції до прототипу, але можна легко переписати їх на окремі функції. Ось код:

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

Цей алгоритм розділений на дві частини; Сама функція дорівнює і функція пошуку числового індексу властивості в масиві / об'єкті. Функція знаходження потрібна лише тому, що indexof знаходить лише числа та рядки, а об'єктів немає.

Можна назвати це так:

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

Функція повертає true або false, в даному випадку true. Алгоритм als дозволяє порівнювати дуже складні об'єкти:

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

Верхній приклад поверне істину, навіть властивості мають інший порядок. Одна невелика деталь, на яку слід звернути увагу: Цей код також перевіряє наявність одного і того ж типу двох змінних, тому "3" не є таким же, як 3.


3

Я бачу відповіді коду спагетті. Не використовуючи жодних сторонніх вугрів, це дуже легко.

По-перше, відсортуйте два об’єкти, ввівши їх ключові назви.

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)

Тоді просто використовуйте рядок для їх порівняння.

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

Це також не працює для глибокої копії, вона має лише одну глибину ітерації.
andras

Я думаю, що @andras означає, що вам потрібно рекурсивно сортувати ключі вкладених об'єктів.
Даві Ліма

2

Я б радив проти хешування або серіалізації (як пропонує рішення JSON). Якщо вам потрібно перевірити, чи два об'єкти рівні, то вам потрібно визначити, що означає рівність. Можливо, всі члени даних в обох об'єктах збігаються, або може бути, що місця розташування пам'яті повинні збігатися (тобто обидві змінні посилаються на один і той же об’єкт у пам'яті), або може бути відповідати лише один член даних у кожному об'єкті.

Нещодавно я розробив об’єкт, конструктор якого створює новий ідентифікатор (починаючи з 1 і збільшуючи до 1) щоразу, коли створюється екземпляр. Цей об’єкт має функцію isEqual, яка порівнює це значення id зі значенням id іншого об'єкта та повертає істинне, якщо вони відповідають.

У цьому випадку я визначив "рівний", що означає значення значень id. Зважаючи на те, що кожен екземпляр має унікальний ідентифікатор, це може бути використане для втілення думки про те, що відповідні об'єкти також займають одне і те ж місце в пам'яті. Хоча це не обов’язково.


2

Корисно вважати два об'єкти рівними, якщо вони мають однакові значення для всіх властивостей і рекурсивно для всіх вкладених об'єктів і масивів. Я також вважаю такими два об'єкти рівними:

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

Аналогічно, масиви можуть мати "відсутні" елементи та невизначені елементи. Я б так само ставився до тих:

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

Функція, яка реалізує це визначення рівності:

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

Вихідний код (включаючи допоміжні функції, GeneralType та uniqueArray): Тест модуля та тест- рунер тут .


2

Я роблю такі припущення з цією функцією:

  1. Ви керуєте об'єктами, які ви порівнюєте, і у вас є лише примітивні значення (тобто не вкладені об'єкти, функції тощо).
  2. У вашому браузері є підтримка Object.keys .

Це слід трактувати як демонстрацію простої стратегії.

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

Це доповнення до всього вищезазначеного, а не заміна. Якщо вам потрібно швидко порівняти об’єкти без необхідності перевіряти зайві рекурсивні випадки. Ось знімок.

Це порівнюється з: 1) рівність кількості власних властивостей, 2) рівність імен ключових, 3) якщо bCompareValues ​​== true, рівність відповідних значень властивостей та їх типів (потрійна рівність)

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

Для порівняння ключів для простих екземплярів об'єктів пар ключ / значення я використовую:

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

Після порівняння ключів достатньо простого додаткового for..inциклу.

Складність O (N * N), N - кількість клавіш.

Я сподіваюсь / здогадуюсь, я визначаю, що об'єкти не матимуть більше 1000 властивостей ...


2

Я знаю, що це трохи старе, але я хотів би додати рішення, яке я придумав для цієї проблеми. У мене був об’єкт, і я хотів дізнатися, коли його дані змінилися. "щось подібне до Object.observe", і що я зробив:

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

Це можна дублювати і створювати інший набір масивів для порівняння значень і ключів. Це дуже просто, оскільки вони тепер є масивами і повертатимуться помилковими, якщо об'єкти мають різний розмір.


1
Це завжди повернеться false, тому що масиви не порівнюються за значенням, наприклад [1,2] != [1,2].
гусек
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.