Как определить равенство для двух объектов JavaScript?

670

Оператор строгого равенства скажет вам, если два типа объектов равны. Однако есть ли способ определить, равны ли два объекта, так же, как значение хэш-кода в Java?

Вопрос переполнения стека Есть ли какая-либо функция hashCode в JavaScript? похож на этот вопрос, но требует более академического ответа. Приведенный выше сценарий демонстрирует, почему это необходимо, и мне интересно, есть ли какое-либо эквивалентное решение .

Сообщество
источник
3
Также посмотрите на этот вопрос stackoverflow.com/q/1068834/1671639
Правин
37
Обратите внимание, что даже в Java a.hashCode() == b.hashCode()это не значит, что aоно равно b. Это необходимое условие, а не достаточное.
Хайнци
Просьба взглянуть на это qs: stackoverflow.com/questions/35933616/…
Forgottofly
2
Если вам нужно сравнивать объекты в вашем коде, вы, вероятно, пишете свой код неправильно. Лучший вопрос может быть: «Как я могу написать этот код, чтобы мне не приходилось сравнивать объекты?»
th317erd
3
@ th317erd Не могли бы вы объяснить, пожалуйста? ...
El Mac,

Ответы:

175

Краткий ответ

Ответ прост: нет, нет универсальных средств для определения того, что объект равен другому в том смысле, который вы имеете в виду. Исключение составляют случаи, когда вы строго думаете о том, что объект не имеет типов.

Длинный ответ

Концепция заключается в методе Equals, который сравнивает два разных экземпляра объекта, чтобы указать, равны ли они на уровне значения. Тем не менее, это зависит от конкретного типа, чтобы определить, как 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 различие между данными и методами размыто.

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

Как насчет объекта, у которого один из существующих методов-прототипов переопределен другой функцией? Можно ли считать его равным другому экземпляру, который в остальном идентичен? На этот вопрос можно ответить только в каждом конкретном случае для каждого типа.

Как указывалось ранее, исключение будет представлять собой объект строго типизированного типа. В этом случае единственный разумный выбор - итеративное и рекурсивное сравнение каждого члена. Даже тогда нужно спросить, какова «ценность» функции?

AnthonyWJones
источник
176
Если вы используете подчеркивание, вы можете просто сделать_.isEqual(obj1, obj2);
chovy
12
@ Суровый, ответ не дал никакого решения, потому что его нет. Даже в Java не существует серебряной маркировки для сравнения равенства объектов, и правильная реализация .equalsметода не является тривиальной, поэтому есть такая тема, посвященная эффективной Java .
2013 г.
3
@ Kumar Harsh, То, что делает два объекта равными, очень специфично для приложения; не каждое свойство объекта обязательно должно приниматься во внимание, поэтому грубое принуждение каждого свойства объекта также не является конкретным решением.
Sethro
гуглил javascript equality object, получил ответ, взял однострочник из комментария @chovy. спасибо
Андреа
Если вы используете угловой, у вас естьangular.equals
боокодер
509

Зачем изобретать велосипед? Дайте Лодаш попробовать. Он имеет ряд обязательных функций, таких как isEqual () .

_.isEqual(object, other);

Он будет грубо проверять каждое значение ключа - как и другие примеры на этой странице - используя ECMAScript 5 и встроенную оптимизацию, если они доступны в браузере.

Примечание. Ранее этот ответ рекомендовал Underscore.js , но lodash проделал лучшую работу по исправлению ошибок и последовательному решению проблем.

CoolAJ86
источник
27
Функция Underscore isEqual очень хороша (но для ее использования вам нужно вытащить их библиотеку - около 3K gzipped).
mckoss
29
если вы посмотрите на то, что дает вам еще
одно
6
Даже если вы не можете позволить себе подчеркивание в качестве зависимости, вытяните функцию isEqual, выполните требования лицензии и продолжайте. На сегодняшний день это самый полный тест на равенство, упомянутый в stackoverflow.
Дейл Андерсон
7
Есть форк Underscore под названием LoDash, и этот автор очень обеспокоен такими проблемами согласованности. Попробуйте с LoDash и посмотрите, что вы получите.
CoolAJ86
6
@mckoss вы можете использовать автономный модуль, если вам не нужна вся библиотека npmjs.com/package/lodash.isequal
Роб Фокс,
161

Оператор равенства по умолчанию в JavaScript для объектов возвращает true, если они ссылаются на одно и то же место в памяти.

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 и сравнение строк может стать сложной вычислительной задачей для сложных объектов в тесных циклах. Для простых объектов это, вероятно, не имеет большого значения, но на самом деле это действительно зависит от вашей конкретной ситуации. Правильное решение может быть таким же простым, как сравнение идентификаторов объектов или проверка каждого свойства, но его правильность полностью определяется проблемной областью.
Даниэль Х Мур
Разве мы не должны сравнивать тип данных ?! вернуть 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; } Это сломалось бы при сравнении двух 'new String (' a ')' в разных окнах. Для равенства значений вам нужно проверить, есть ли String.isString на обоих объектах, а затем использовать свободную проверку равенства «a == b».
Трийнко
1
Существует огромная разница между «ценностным» равенством и «строгим» равенством, и они не должны реализовываться одинаково. Равенство значений не должно заботиться о типах, кроме базовой структуры, которая является одной из следующих 4: «объект» (т.е. набор пар ключ / значение), «число», «строка» или «массив». Вот и все. Все, что не является числом, строкой или массивом, должно сравниваться как набор пар ключ / значение, независимо от того, что является конструктором (cross-window-safe). При сравнении объектов приравнивайте значение буквенных чисел и экземпляров Number, но не приводите строки к числам.
Трийнко
50

Если вы используете библиотеку JSON, вы можете закодировать каждый объект как JSON, а затем сравнить полученные строки на равенство.

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

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

ПРИМЕЧАНИЕ. Хотя этот ответ будет работать во многих случаях, как отметили несколько человек в комментариях, это проблематично по ряду причин. В большинстве случаев вы захотите найти более надежное решение.

Джоэл Анаир
источник
91
Интересно, но немного сложно на мой взгляд. Например, можете ли вы на 100% гарантировать, что свойства объекта будут генерироваться всегда в одном и том же порядке?
Гвидо
25
Это хороший вопрос, и возникает другой вопрос о том, являются ли два объекта с одинаковыми свойствами в разных порядках действительно равными или нет. Я полагаю, зависит от того, что вы подразумеваете под равным.
Джоэл Анаир
11
Обратите внимание, что большинство кодировщиков и строковых преобразователей игнорируют функции и преобразуют неопределенные числа, такие как NaN, в null.
Стивен Белэнджер
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, используя предложение стрелы и функции стрелки 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);
}
atmin
источник
5
Вы могли бы заменить reduceс everyупрощать.
Джиб
1
@nonkertompf уверен , что он мог: Object.keys(x).every(key => deepEqual(x[key], y[key])).
гиб
2
Это не удается, когда вы сравниваете две даты
Грег
3
deepEqual ({}, []) возвращает true
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" } } но вы поняли идею.

Как указывалось в FOR, вам, возможно, придется адаптировать это для своих собственных целей, например: разные классы могут иметь разные определения «равно». Если вы просто работаете с простыми объектами, вышеприведенного может быть достаточно, в противном случае можно использовать пользовательскую MyClass.equals()функцию.

nickf
источник
Это длинный метод, но он полностью тестирует объекты, не делая никаких предположений о порядке свойств в каждом объекте.
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 Цитаты нужны;) Я вспоминаю тестирование этого в нескольких браузерах, получая согласованные результаты. Но, конечно, никто не может гарантировать, что поведение останется таким же в будущих браузерах / движках. В лучшем случае это хитрость (как уже упоминалось в ответе), и я не имел в виду, что это верный способ сравнения объектов.
Ates Goral
1
Конечно, вот несколько указателей: спецификация ECMAScript говорит, что объект "неупорядочен" ; и этот ответ для фактического расходящегося поведения в текущих браузерах.
Джо Лисс
2
@JoLiss Спасибо за это! Но обратите внимание, я никогда не претендовал на сохранение порядка между кодом и скомпилированным объектом. Я требовал сохранения порядка свойств, значения которых заменяются на месте. Это было ключом к моему решению: использовать миксин, чтобы просто перезаписать значения свойств. Предполагая, что реализации, как правило, предпочитают использовать своего рода хэш-карту, замена только значений должна сохранить порядок ключей. Именно это я и проверил в разных браузерах.
Атес Горал
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

Простейшие и логичные решения для сравнения всего, например Object, Array, String, Int ...

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

Замечания:

  • вам нужно заменить val1и val2вашим объектом
  • для объекта, вы должны отсортировать (по ключу) рекурсивно для обоих сторонних объектов
Пратик Бхалодия
источник
6
Я предполагаю, что это не будет работать во многих случаях, потому что порядок ключей в объектах не имеет значения - разве JSON.stringifyалфавитное переупорядочение? (Что я не могу найти документально .)
Брэм Ванрой
да, вы правы ... для объекта, вы должны сортировать рекурсивно для обоих побочных объектов
Pratik Bhalodiya
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
хорошая идея (в моем случае сравниваемые объекты - просто пары ключ / значение, никаких особых вещей)
мех
10

Вот решение ESES / 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;
}

Кроме того, он рекурсивный, поэтому он также может проверять наличие глубокого равенства, если вы так его называете.

Zac
источник
Небольшое исправление: перед тем, как проходить через каждый подпор в a и b, добавьте эту проверку, если (Object.getOwnPropertyNames (a) .length! == Object.getOwnPropertyNames (b) .length) возвращает false
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

Vaelin
источник
его производительность должна быть хорошей. Не беспокойся. Даже я использую это в сложных сценариях. Когда мы используем это, нам не нужно беспокоиться, если свойство объекта содержит объект или массив. 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

Bentaiba Miled Basma
источник
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
Это не делает то, что хочет OP, поскольку оно будет совпадать только в том случае, если оба объекта имеют одинаковые ключи, которые, как они утверждают, они не будут иметь. Это также потребовало бы, чтобы ключи были в том же порядке, что также не совсем разумно.
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(''));
}

Еще одна полезная вещь в этом методе - вы можете фильтровать сравнения, передавая функцию «replacer» в функции JSON.stringify ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON / stringify # Example_of_using_replacer_parameter ). Далее будут сравниваться все ключи объектов, которые называются «сумасшедший»:

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;
});
th317erd
источник
1
О, я тоже забыл, но эту функцию можно ускорить, сначала протестировав объект, выравнивающий и освобождающий от обязательств раньше, если это один и тот же объект: if (obj1 === obj2) return true;
th317erd
11
areEqual({a: 'b'}, {b: 'a'})получает trueтогда?
Ok
Да, я понял после публикации, что у этого "решения" есть проблемы. Требуется немного больше работы в алгоритме сортировки, чтобы фактически работать должным образом.
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;
}
Liam
источник
2
Я закончил тем, что использовал вариант этого. Спасибо за идею подсчета участников!
NateS
2
Будьте очень осторожны с изменениями Object.prototype- в подавляющем большинстве случаев это не рекомендуется (например, добавления появляются во всех циклах for..in). Возможно посмотрим 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;
}

Этот алгоритм разбит на две части; Функция equals и функция для поиска числового индекса свойства в массиве / объекте. Функция find нужна только потому, что indexof находит только числа и строки, а не объекты.

Это можно назвать так:

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

Функция возвращает true или false, в этом случае true. Алгоритм также позволяет сравнивать очень сложные объекты:

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

Верхний пример вернет true, даже если свойства имеют другой порядок. Одна небольшая деталь, на которую нужно обратить внимание: этот код также проверяет наличие одного и того же типа двух переменных, поэтому «3» не совпадает с «3».

Sir_baaron
источник
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 другого объекта и возвращает true, если они совпадают.

В этом случае я определил «равно» как означающее совпадение значений идентификатора. Учитывая, что каждый экземпляр имеет уникальный идентификатор, это можно использовать для реализации идеи о том, что соответствующие объекты также занимают одно и то же место в памяти. Хотя это не обязательно.

Бернард Игири
источник
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): модульный тест и тестовый прогон здесь .

mckoss
источник
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 свойств ...

Hefeust CORTES
источник
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)
}

Это здесь можно продублировать и создать другой набор массивов для сравнения значений и ключей. Это очень просто, потому что они теперь являются массивами и будут возвращать false, если объекты имеют разные размеры.

inoabrian
источник
1
Это всегда будет возвращаться false, потому что массивы не сравниваются по значению, например [1,2] != [1,2].
стрела