Вопрос по javascript, object, comparison, object-comparison – Сравнение объектов в JavaScript [дубликаты]

879

На этот вопрос уже есть ответ:

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

Какой лучший способ сравнить объекты в JavaScript?

Пример

var user1 = {name : "nerd", org: "dev"};
var user2 = {name : "nerd", org: "dev"};
var eq = user1 == user2;
alert(eq); // gives false

Я знаю это два объекта равны, если они ссылаются на один и тот же объект, но есть ли способ проверить, имеют ли они одинаковые значения атрибутов?

У меня работает следующий способ, но единственная ли это возможность?

var eq = Object.toJSON(user1) == Object.toJSON(user2);
alert(eq); // gives true
Используйте Лодаш. ЭтоisEqual метод делает именно то, что вы хотите. Adam Fraser
использование подчеркивания,_.isEqual( obj1 , obj2 ) Sai Ram
use Facebook.github.io / неизменный-JS и эта операция будет супер легкой и быстрой Lukas
Тестирование на (глубокое) равенство - довольно сложная вещь, чтобы получить право. отправляйтесь на Github.com / loveencounterflow / jseq чтобы увидеть тестовый набор популярныхequal() реализации, которые уже охватывают многие крайние случаи. обсуждение в документах также довольно основательно. flow
Я добавил для этого ответ Stackoverflow.com / а / 16788517/1414809 Ebrahim Byagowi

Ваш Ответ

10   ответов
1060

_proto_ рекурсивно и доступ ко всем не перечисляемым свойствам, но это работает только в Firefox.

Так что лучшее, что я могу сделать, это угадать сценарии использования.

1) Быстрый и ограниченный.

Работает, когда у вас есть простые объекты в стиле JSON без методов и узлов DOM внутри:

 JSON.stringify(obj1) === JSON.stringify(obj2) 

ПОРЯДОК СВОЙСТВ ВАЖЕН, поэтому этот метод вернет false для следующих объектов:

 x = {a: 1, b: 2};
 y = {b: 2, a: 1};
2) Медленный и более общий.

Сравнивает объекты, не копаясь в прототипах, затем рекурсивно сравнивает проекции свойств, а также сравнивает конструкторы.

Это почти правильный алгоритм:

function deepCompare () {
  var i, l, leftChain, rightChain;

  function compare2Objects (x, y) {
    var p;

    // remember that NaN === NaN returns false
    // and isNaN(undefined) returns true
    if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
         return true;
    }

    // Compare primitives and functions.     
    // Check if both arguments link to the same object.
    // Especially useful on the step where we compare prototypes
    if (x === y) {
        return true;
    }

    // Works in case when functions are created in constructor.
    // Comparing dates is a common scenario. Another built-ins?
    // We can even handle functions passed across iframes
    if ((typeof x === 'function' && typeof y === 'function') ||
       (x instanceof Date && y instanceof Date) ||
       (x instanceof RegExp && y instanceof RegExp) ||
       (x instanceof String && y instanceof String) ||
       (x instanceof Number && y instanceof Number)) {
        return x.toString() === y.toString();
    }

    // At last checking prototypes as good as we can
    if (!(x instanceof Object && y instanceof Object)) {
        return false;
    }

    if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
        return false;
    }

    if (x.constructor !== y.constructor) {
        return false;
    }

    if (x.prototype !== y.prototype) {
        return false;
    }

    // Check for infinitive linking loops
    if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
         return false;
    }

    // Quick checking of one object being a subset of another.
    // todo: cache the structure of arguments[0] for performance
    for (p in y) {
        if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
            return false;
        }
        else if (typeof y[p] !== typeof x[p]) {
            return false;
        }
    }

    for (p in x) {
        if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
            return false;
        }
        else if (typeof y[p] !== typeof x[p]) {
            return false;
        }

        switch (typeof (x[p])) {
            case 'object':
            case 'function':

                leftChain.push(x);
                rightChain.push(y);

                if (!compare2Objects (x[p], y[p])) {
                    return false;
                }

                leftChain.pop();
                rightChain.pop();
                break;

            default:
                if (x[p] !== y[p]) {
                    return false;
                }
                break;
        }
    }

    return true;
  }

  if (arguments.length < 1) {
    return true; //Die silently? Don't know how to handle such case, please help...
    // throw "Need two or more arguments to compare";
  }

  for (i = 1, l = arguments.length; i < l; i++) {

      leftChain = []; //Todo: this can be cached
      rightChain = [];

      if (!compare2Objects(arguments[0], arguments[i])) {
          return false;
      }
  }

  return true;
}

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

объекты с другой структурой прототипа, но с одинаковой проекциейфункции могут иметь одинаковый текст, но относиться к разным замыканиям

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

Проверка противundefined не будет выполнен, если свойство определено, но установлено значениеundefined ценность. Использоватьinператор @ вместоtypeof чтобы избежать этого:p in x. Также сравнение функций по строковому значению крайне ненадежно. Помимо обычных причин неудачной декомпозиции функций, также очень часто иметь две функции с одинаковым кодом, но с совершенно разным поведением из-за замыканий. например. любая функция, созданная в jQuery's$.proxy или прототипFunction#bind. Я бы просто сравнил идентичность функций. bobince
Regarding 1) "ПОРЯДОК СВОЙСТВ ВАЖЕН, поэтому этот метод вернет false для следующих объектов:" Это не обязательно правда. Этот методможе возвращениеfalse для этих объектов, а может и нет. Там нет никакой гарантии в любом случае. Вот почему мы не используемJSON.stringify сравнение для сравнения объектов. Там нет никаких гарантий порядка. user2437417
Я думаю, тебе стоит использовать Тождественны оператор сравнения:===, причина{ a: 5 } а также{ a: "5.0" } не равны или нет? Crozin
Вы не должны расширять Object.prototype, если этого можно избежать. Это вызывает неприятные проблемы, такие как взломfor(var key in someObject) если нетif(!someObject.hasOwnProperty(key)) continue; внутри этого цикла. ThiefMaster♦
сравнение функций неверно: функции могут иметь одинаковый текст, но ссылаться на разные замыкания. Лучше просто вернутьthis[p] === x[p]. Eamon Nerbonne
163

Object.equals = function( x, y ) {
  if ( x === y ) return true;
    // if both x and y are null or undefined and exactly the same

  if ( ! ( x instanceof Object ) || ! ( y instanceof Object ) ) return false;
    // if they are not strictly equal, they both need to be Objects

  if ( x.constructor !== y.constructor ) return false;
    // they must have the exact same prototype chain, the closest we can do is
    // test there constructor.

  for ( var p in x ) {
    if ( ! x.hasOwnProperty( p ) ) continue;
      // other properties were tested using x.constructor === y.constructor

    if ( ! y.hasOwnProperty( p ) ) return false;
      // allows to compare x[ p ] and y[ p ] when set to undefined

    if ( x[ p ] === y[ p ] ) continue;
      // if they have the same strict value or identity then they are equal

    if ( typeof( x[ p ] ) !== "object" ) return false;
      // Numbers, Strings, Functions, Booleans must be strictly equal

    if ( ! Object.equals( x[ p ],  y[ p ] ) ) return false;
      // Objects and Arrays must be tested recursively
  }

  for ( p in y ) {
    if ( y.hasOwnProperty( p ) && ! x.hasOwnProperty( p ) ) return false;
      // allows x[ p ] to be set to undefined
  }
  return true;
}

При разработке этого решения я уделил особое внимание угловым случаям, эффективности, но в то же время пытался найти простое решение, которое работает, надеюсь, с некоторой элегантностью. JavaScript позволяет какзначение NUL а также Неопределенный свойства и объекты имеют цепочки прототипов это может привести к очень разным поведениям, если не проверено.

Сначала я решил продлить Object вместо того Object.prototype, в основном потому, чтозначение NUL не может быть одним из объектов сравнения, и я считаю, чтозначение NUL должен быть допустимым объектом для сравнения с другим. Есть и другие законные опасения, отмеченные другими в отношении расширения Object.prototype относительно возможных побочных эффектов на код другого пользователя.

Особая осторожность должна быть предпринята, чтобы учесть возможность того, что JavaScript позволяет свойствам объекта быть установленным Неопределенный, то есть существуют свойства, значения которых установлены на Неопределенный. Приведенное выше решение проверяет, что оба объекта имеют одинаковые свойства, установленные в Неопределенный сообщить о равенстве. Это может быть достигнуто только путем проверки наличия свойств с помощью Object.hasOwnProperty (имя_свойства). Также обратите внимание, что JSON.stringify () удаляет свойства, для которых установлено Неопределенный, и поэтому при использовании этой формы сравнения будут игнорироваться свойства, для которых установлено значение Неопределенный.

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

Два объекта должны быть одинаковыми цепочка прототипов, а не только те же свойства. Это можно проверить только через кросс-браузер, сравнконструкто обоих объектов для строгого равенства. ECMAScript 5 позволит протестировать их фактический прототип, используя Object.getPrototypeOf (). Некоторые веб-браузеры также предлагают__ прото __ свойство, которое делает то же самое. Возможное улучшение приведенного выше кода позволит использовать один из этих методов, когда это возможно.

Использование строгих сравнений имеет первостепенное значение, потому что2 не следует считать равным "2,0000" ниложны следует считать равнымзначение NUL, Неопределенный, или0.

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

Не требуется более двух циклов: первый проверяет свойства из левого объекта, второй проверяет свойства справа и проверяет только существование (не значение), чтобы поймать эти свойства, которые определены с помощью Неопределенный ценность

В целом этот код обрабатывает большинство угловых случаев всего в 16 строках кода (без комментариев).

Обновление (13.08.2015). Я реализовал лучшую версию, как функциюvalue_equals (), что быстрее, правильно обрабатывает угловые случаи, такие как NaN и 0, отличные от -0, опционально предписывая порядок свойств объектов и проверяя циклические ссылки, поддержанные более чем 100 автоматических тестов как часть Toubkal тестовый пакет проекта.

На самом деле я нашел способ определить, является ли объект циклическим. Добавил его в другую скрипку, Jsfiddle.net / mendesjuan / uKtEy / 1 Теперь функция выдает ошибку, если объект циклический, что обидно, потому что ваша оригинальная версия работала для циклических объектов, если два объекта указывали на один и тот же объект Jsfiddle.net / mendesjuan / uKtEy / 2 Juan Mendes
Я использую эту функцию в своем коде. Только что обнаружил, что у него не хватает стека при тестировании циклических объектов. Я не реализовал это, потому что я не нуждаюсь в этом сейчас, но хотел сообщить Вам. Jsfiddle.net / mendesjuan / uKtEy Juan Mendes
@ JeanVincent Простая проверка на бесконечную рекурсию: Object.equals = функция (x, y, глубина) {if (deep> = 10) return false; ... if (! Object.equals (x [p], y [p], (глубина || 0) + 1)) return false; .... Ivan Nikitin
@ JuanMendes, если вам нужна версия, которая тестирует циклические объекты, гораздо более эффективным способом было бы объединить циклический тест в Object: equals (), чтобы проверить только одно свойство прямо перед рекурсией: обновленная скрипта здесь Jsfiddle.net / uKtEy / 3 Jean Vincent
почему вы хотите проверить прототип цепочки? Если вы на самом деле заботитесь о личности и не видимом поведении, просто используйте===. Если вы заботитесь о видимых, читаемых свойствах, то цепочка прототипов не имеет значения. Eamon Nerbonne
23
  Utils.compareObjects = function(o1, o2){
    for(var p in o1){
        if(o1.hasOwnProperty(p)){
            if(o1[p] !== o2[p]){
                return false;
            }
        }
    }
    for(var p in o2){
        if(o2.hasOwnProperty(p)){
            if(o1[p] !== o2[p]){
                return false;
            }
        }
    }
    return true;
};

hasOwnProperty гарантирует, что «p» не является функцией. jBelanger
Зачем нам нужноhasOwnproperty проверить, перебираем ли мы уже ключи p? TechnoCorner
16

числами, строками, датами и, конечно же, обычными вложенными объектами javascrip

Объекты считаются эквивалентными, когда

Они точно равны на=== (Строка и номер распаковываются в первую очередь, чтобы гарантировать42 эквивалентноNumber(42)) или они обе даты и имеют одинаковыеvalueOf() или они оба одного типа и не равны нулю и ... они не являются объектами и равны на== (ловит числа / строки / логические значения)or, игнорируя свойства с помощьюundefined значение они имеют одинаковые свойства, все из которых считаются рекурсивно эквивалентными.

Функции не считаются идентичными по функциональному тексту. Этого теста недостаточно, потому что функции могут иметь разные замыкания. Функции считаются равными, только если=== говорит (но вы можете легко расширить это эквивалентное отношение, если захотите).

Бесконечные циклы, потенциально вызванные круговыми структурами данных, исключаются. КогдаareEquivalent пытается опровергнуть равенство и рекурсивно обращается к свойствам объекта, чтобы отслеживать объекты, для которых необходимо это субсравнение. Если равенство может быть опровергнуто, то некоторый путь достижимых свойств отличается между объектами, и тогда должен быть кратчайший такой достижимый путь, и этот кратчайший достижимый путь не может содержать циклы, присутствующие в обоих путях; то есть нормально принимать равенство при рекурсивном сравнении объектов. Предположение хранится в свойствеareEquivalent_Eq_91_2_34, который удаляется после использования, но если граф объектов уже содержит такое свойство, поведение не определено. Использование такого свойства маркера необходимо, потому что javascript не поддерживает словари, использующие произвольные объекты в качестве ключей.

function unwrapStringOrNumber(obj) {
    return (obj instanceof Number || obj instanceof String 
            ? obj.valueOf() 
            : obj);
}
function areEquivalent(a, b) {
    a = unwrapStringOrNumber(a);
    b = unwrapStringOrNumber(b);
    if (a === b) return true; //e.g. a and b both null
    if (a === null || b === null || typeof (a) !== typeof (b)) return false;
    if (a instanceof Date) 
        return b instanceof Date && a.valueOf() === b.valueOf();
    if (typeof (a) !== "object") 
        return a == b; //for boolean, number, string, xml

    var newA = (a.areEquivalent_Eq_91_2_34 === undefined),
        newB = (b.areEquivalent_Eq_91_2_34 === undefined);
    try {
        if (newA) a.areEquivalent_Eq_91_2_34 = [];
        else if (a.areEquivalent_Eq_91_2_34.some(
            function (other) { return other === b; })) return true;
        if (newB) b.areEquivalent_Eq_91_2_34 = [];
        else if (b.areEquivalent_Eq_91_2_34.some(
            function (other) { return other === a; })) return true;
        a.areEquivalent_Eq_91_2_34.push(b);
        b.areEquivalent_Eq_91_2_34.push(a);

        var tmp = {};
        for (var prop in a) 
            if(prop != "areEquivalent_Eq_91_2_34") 
                tmp[prop] = null;
        for (var prop in b) 
            if (prop != "areEquivalent_Eq_91_2_34") 
                tmp[prop] = null;

        for (var prop in tmp) 
            if (!areEquivalent(a[prop], b[prop]))
                return false;
        return true;
    } finally {
        if (newA) delete a.areEquivalent_Eq_91_2_34;
        if (newB) delete b.areEquivalent_Eq_91_2_34;
    }
}
-1, вы можете использовать массив для записи цепочки рекурсии, а затем предотвратить циклы Dan
@ Дана просто предотвратить бесконечную рекурсию недостаточно: вы все еще хотите знать, эквивалентны ли циклические объекты или нет. Eamon Nerbonne
4

вы можете использовать методы method.toSource () или method.toString ().

@ snz3 - существует серьезная проблема с пробелами, пропущенными точками с запятой и фигурными скобками и похожими синтаксическими различиями, которые могут иметь или не иметь влияние, и их трудно определить без анализа, то есть отсоединения от формата необработанной строки. Есть также проблема колебания состояния и прототипирования. В основном строки не достаточно хороши для записи состояния двух объектов. annakata
действительно недостаточно хорош по причинам, которые я описал annakata
Носредна, да. Это даст вам фактический текст функции. Аннаката, я не понимаю, что недостаточно хорошо и что ты на самом деле пытаешься сделать. Не могли бы вы уточнить немног Nicolas R
Чтобы вы могли прокручивать элементы объекта и проверять типы, а затем использовать toSource () или toString (), когда найдете функцию? Nosredna
3

Object.prototype.equals = function(b) {
    var a = this;
    for(i in a) {
        if(typeof b[i] == 'undefined') {
            return false;
        }
        if(typeof b[i] == 'object') {
            if(!b[i].equals(a[i])) {
                return false;
            }
        }
        if(b[i] != a[i]) {
            return false;
        }
    }
    for(i in b) {
        if(typeof a[i] == 'undefined') {
            return false;
        }
        if(typeof a[i] == 'object') {
            if(!a[i].equals(b[i])) {
                return false;
            }
        }
        if(a[i] != b[i]) {
            return false;
        }
    }
    return true;
}

var a = {foo:'bar', bar: {blub:'bla'}};
var b = {foo:'bar', bar: {blub:'blob'}};
alert(a.equals(b)); // alert's a false
Это плохая идея. Модификация Object.prototype может привести к непредвиденным последствиям. MarcF
Вы не можете работать без библиотеки JSON, она является частью стандартной библиотеки Javascript: Developer.mozilla.org / EN-US / Docs / Web / JavaScript / Справочник / ... Gerard Simpson
21

не единственный способ - вы могли бы создать прототип метода (для Object здесь, но я, конечно, не рекомендовал бы использовать Object для живого кода) для репликации методов сравнения в стиле C # / Java.

Редакция, так как общий пример кажется ожидаемым:

Object.prototype.equals = function(x)
{
    for(p in this)
    {
        switch(typeof(this[p]))
        {
            case 'object':
                if (!this[p].equals(x[p])) { return false }; break;
            case 'function':
                if (typeof(x[p])=='undefined' || (p != 'equals' && this[p].toString() != x[p].toString())) { return false; }; break;
            default:
                if (this[p] != x[p]) { return false; }
        }
    }

    for(p in x)
    {
        if(typeof(this[p])=='undefined') {return false;}
    }

    return true;
}

Обратите внимание, что методы тестирования с помощью toString () - это абсолютно недостаточно хорош но метод, который был бы приемлемым, очень труден из-за проблемы пробелов, имеющих значение или нет, не говоря уже о методах и методах синонимов, дающих один и тот же результат с различными реализациями.А такж проблемы прототипирования объектов в целом.

Вы просматриваете только элементы "this". Разве ваш метод equals не вернет true, если «this» является подмножеством большего «x»? Разве вы не должны добавить цикл for (p in x)? Nosredna
Кроме того, было бы полезно рекурсивное сравнение, использующее equals () для сравнения свойств, которые являются объектами. molf
Код неисправен. o1.equals (o2) возвращает true, если все свойства o1 равны свойствам o2. Но если у o2 также есть другие свойства, которых у o1 нет, объекты могут быть неправильно помечены как равные. molf
thx, и тот же вопрос, что и выше ... это работает с объектными методами? spankmaster79
Да и нет. Это не так, как написано, потому что эти два метода будут разными экземплярами объекта (если они не являются прототипами), но вы можете адаптировать это, чтобы определить, что методы эквивалентны (хотя это не так просто). annakata
11

и, похоже, он работает. проверьте утверждения:


function countProps(obj) {
    var count = 0;
    for (k in obj) {
        if (obj.hasOwnProperty(k)) {
            count++;
        }
    }
    return count;
};

function objectEquals(v1, v2) {

    if (typeof(v1) !== typeof(v2)) {
        return false;
    }

    if (typeof(v1) === "function") {
        return v1.toString() === v2.toString();
    }

    if (v1 instanceof Object && v2 instanceof Object) {
        if (countProps(v1) !== countProps(v2)) {
            return false;
        }
        var r = true;
        for (k in v1) {
            r = objectEquals(v1[k], v2[k]);
            if (!r) {
                return false;
            }
        }
        return true;
    } else {
        return v1 === v2;
    }
}

assert.isTrue(objectEquals(null,null));
assert.isFalse(objectEquals(null,undefined));

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

assert.isTrue(objectEquals(function(x){return x;},function(x){return x;}));
assert.isFalse(objectEquals(function(x){return x;},function(y){return y+2;}));
5

ned. Если вам не нужна такая строгая проверка, удалите один "=" войти в систему " это [р]! == х [р] "внутри кода.

Object.prototype.equals = function(x){
    for (var p in this) {
        if(typeof(this[p]) !== typeof(x[p])) return false;
        if((this[p]===null) !== (x[p]===null)) return false;
        switch (typeof(this[p])) {
            case 'undefined':
                if (typeof(x[p]) != 'undefined') return false;
                break;
            case 'object':
                if(this[p]!==null && x[p]!==null && (this[p].constructor.toString() !== x[p].constructor.toString() || !this[p].equals(x[p]))) return false;
                break;
            case 'function':
                if (p != 'equals' && this[p].toString() != x[p].toString()) return false;
                break;
            default:
                if (this[p] !== x[p]) return false;
        }
    }
    return true;
}

Тогда я поставил следующие предметы:

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 f = {a: 'text', b:[1,0], f: function(){ this.f = this.b; }};
var g = {a: 'text', b:[1,0], f: function(){ this.f = this.b; }};
var h = {a: 'text', b:[1,0], f: function(){ this.a = this.b; }};
var i = {
    a: 'text',
    c: {
        b: [1, 0],
        f: function(){
            this.a = this.b;
        }
    }
};
var j = {
    a: 'text',
    c: {
        b: [1, 0],
        f: function(){
            this.a = this.b;
        }
    }
};
var k = {a: 'text', b: null};
var l = {a: 'text', b: undefined};

a == b ожидаемая истина; вернул true

a == c ожидается false; вернул false

c == d ожидается ложь; вернул false

a == e ожидается false; вернул false

f == g ожидаемая истина; вернул true

h == g ожидается ложь; вернул false

i == j ожидал истины; вернул true

d == k ожидается false; вернул false

k == я ожидал ложного; вернул false

+ 1 для тестирования конструктора или подчиненных объектов. Но почему бы не протестировать основной объект? И почему бы не протестировать функции по ссылке вместо сравнения строк с помощью toString (), это медленно и неточно. Jean Vincent
4

многое из этого потока интегрировано (то же самое для тестовых случаев):

Object.defineProperty(Object.prototype, "equals", {
    enumerable: false,
    value: function (obj) {
        var p;
        if (this === obj) {
            return true;
        }

        // some checks for native types first

        // function and sring
        if (typeof(this) === "function" || typeof(this) === "string" || this instanceof String) { 
            return this.toString() === obj.toString();
        }

        // number
        if (this instanceof Number || typeof(this) === "number") {
            if (obj instanceof Number || typeof(obj) === "number") {
                return this.valueOf() === obj.valueOf();
            }
            return false;
        }

        // null.equals(null) and undefined.equals(undefined) do not inherit from the 
        // Object.prototype so we can return false when they are passed as obj
        if (typeof(this) !== typeof(obj) || obj === null || typeof(obj) === "undefined") {
            return false;
        }

        function sort (o) {
            var result = {};

            if (typeof o !== "object") {
                return o;
            }

            Object.keys(o).sort().forEach(function (key) {
                result[key] = sort(o[key]);
            });

            return result;
        }

        if (typeof(this) === "object") {
            if (Array.isArray(this)) { // check on arrays
                return JSON.stringify(this) === JSON.stringify(obj);                
            } else { // anyway objects
                for (p in this) {
                    if (typeof(this[p]) !== typeof(obj[p])) {
                        return false;
                    }
                    if ((this[p] === null) !== (obj[p] === null)) {
                        return false;
                    }
                    switch (typeof(this[p])) {
                    case 'undefined':
                        if (typeof(obj[p]) !== 'undefined') {
                            return false;
                        }
                        break;
                    case 'object':
                        if (this[p] !== null 
                                && obj[p] !== null 
                                && (this[p].constructor.toString() !== obj[p].constructor.toString() 
                                        || !this[p].equals(obj[p]))) {
                            return false;
                        }
                        break;
                    case 'function':
                        if (this[p].toString() !== obj[p].toString()) {
                            return false;
                        }
                        break;
                    default:
                        if (this[p] !== obj[p]) {
                            return false;
                        }
                    }
                };

            }
        }

        // at least check them with JSON
        return JSON.stringify(sort(this)) === JSON.stringify(sort(obj));
    }
});

Вот мой TestCase:

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

    assertTrue("String", "hi".equals("hi"));
    assertTrue("Number", new Number(5).equals(5));
    assertFalse("Number", new Number(5).equals(10));
    assertFalse("Number+String", 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}}));

    assertTrue("Function", (function(x){return x;}).equals(function(x){return x;}));
    assertFalse("Function", (function(x){return x;}).equals(function(y){return y+2;}));

    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 f = {a: 'text', b:[1,0], f: function(){ this.f = this.b; }};
    var g = {a: 'text', b:[1,0], f: function(){ this.f = this.b; }};
    var h = {a: 'text', b:[1,0], f: function(){ this.a = this.b; }};
    var i = {
        a: 'text',
        c: {
            b: [1, 0],
            f: function(){
                this.a = this.b;
            }
        }
    };
    var j = {
        a: 'text',
        c: {
            b: [1, 0],
            f: function(){
                this.a = this.b;
            }
        }
    };
    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(f.equals(g));
    assertFalse(h.equals(g));
    assertTrue(i.equals(j));
    assertFalse(d.equals(k));
    assertFalse(k.equals(l));
Вы стреляете массивы, но в массиве могут быть сложные объекты Dan
JSON.stringify () удаляет свойства, для которых установлено значение undefined, и поэтому при сравнении с использованием этой формы игнорируются свойства, для которых установлено значение undefined: assertFalse ([1,2, null] .equals ([1,2, undefined]) ). Jean Vincent
Разве этот тест не должен утверждать ложь вместо истины, потому что один является экземпляром объекта, а другой является примитивным? assertTrue ("Число", новый номер (5) .equals (5)); Pete Alvin

Похожие вопросы