Как определить равенство для двух объектов JavaScript? - PullRequest
547 голосов
/ 14 октября 2008

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

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

Ответы [ 55 ]

452 голосов
/ 07 июля 2010

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

_.isEqual(object, other);

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

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

160 голосов
/ 14 октября 2008

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

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

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

Концепция заключается в методе 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 различие между данными и методом размыто.

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

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

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

145 голосов
/ 20 мая 2009

Оператор равенства по умолчанию в 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
71 голосов
/ 27 сентября 2013

Если вы работаете в 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>
58 голосов
/ 28 мая 2013

Это моя версия. Он использует новую функцию 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 } }));
47 голосов
/ 14 октября 2008

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

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

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

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

31 голосов
/ 03 октября 2015

Краткий функционал 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);
}
20 голосов
/ 14 июня 2012

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

17 голосов
/ 09 октября 2017

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

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

Примечание:

  • вам нужно заменить val1 и val2 на ваш объект
  • для объекта необходимо выполнить рекурсивную сортировку (по ключу) для обоих боковых объектов
17 голосов
/ 14 октября 2008

Вы пытаетесь проверить, равны ли два объекта? т.е. их свойства равны?

Если это так, вы, вероятно, заметили эту ситуацию:

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().

...