두 JavaScript 객체의 동등성을 결정하는 방법은 무엇입니까?


670

엄격한 항등 연산자는 두 객체 유형 이 같은지 알려줍니다 . 그러나 Java 의 해시 코드과 같이 두 객체가 동일한 지 알 수있는 방법이 있습니까?

스택 오버플로 질문 JavaScript에 해시 코드 함수가 있습니까? 이 질문과 비슷하지만보다 학문적 인 답변이 필요합니다. 위의 시나리오는 왜 필요한지 보여 주며 동등한 솔루션 이 있는지 궁금합니다 .


3
또한이 질문에 대하여 stackoverflow.com/q/1068834/1671639
Praveen

37
그 주에도 자바, a.hashCode() == b.hashCode()않습니다 하지 그 의미 a와 같다 b. 충분한 조건이 아니라 필요한 조건입니다.
Heinzi

Pls는이 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;
     }
  }

이 경우 cMyClass의 두 인스턴스가 동일 a하고 b중요한지 여부를 결정하는 것은 실제로 중요하지 않습니다 . 경우에 c따라 인스턴스마다 다를 수 있지만 비교 중에는 중요하지 않습니다.

이 문제는 멤버 자체가 유형의 인스턴스 일 수도 있고 모두 동등성을 결정하는 수단이 필요한 경우에 적용됩니다.

더 복잡한 것은 JavaScript에서 데이터와 메소드의 구별이 흐려진다는 것입니다.

객체는 이벤트 핸들러로 호출 될 메소드를 참조 할 수 있으며 이는 '값 상태'의 일부로 간주되지 않을 것입니다. 다른 객체에는 중요한 계산을 수행하여 단순히 다른 함수를 참조하기 때문에이 인스턴스를 다른 인스턴스와 다르게 만드는 함수가 할당 될 수 있습니다.

기존 프로토 타입 메소드 중 하나가 다른 함수로 대체 된 오브젝트는 어떻습니까? 다른 인스턴스와 동일한 것으로 간주 될 수 있습니까? 이 질문은 각 유형에 대한 각 특정 사례에서만 답할 수 있습니다.

앞에서 언급했듯이 예외는 엄격하게 유형이없는 객체입니다. 이 경우에 현명한 선택은 각 멤버의 반복적이고 재귀적인 비교입니다. 그럼에도 불구하고 함수의 '가치'가 무엇인지 물어봐야합니까?


176
밑줄을 사용하는 경우 다음을 수행 할 수 있습니다._.isEqual(obj1, obj2);
chovy

12
@Harsh, 답변이 없기 때문에 해결책을 제시하지 못했습니다. Java에서도 객체 평등 비교에 대한 은색 글 머리 기호가 없으며 .equals메소드 를 올바르게 구현하는 것이 사소한 것이 아니기 때문에 Effective Java 전용 주제가 있습니다 .
lcn

3
@ Kumar Harsh, 두 객체를 동일하게 만드는 것은 응용 프로그램에 따라 다릅니다. 물체의 모든 특성을 반드시 고려해야하는 것은 아니므로 물체의 모든 특성을 강요하는 것은 구체적인 해결책이 아닙니다.
sethro

googled javascript equality object, tl; dr 응답이 @chovy 댓글에서 한 줄짜리를 가져 왔습니다. 감사합니다
Andrea

각도를 사용하는 경우angular.equals
보트 코더

507

왜 바퀴를 재발 명합니까? 부여 Lodash을 시도합니다. isEqual () 과 같은 많은 필수 함수가 있습니다 .

_.isEqual(object, other);

이 페이지의 다른 예제와 마찬가지로 ECMAScript 5 및 브라우저에서 사용 가능한 경우 기본 최적화를 사용하여 각 키 값을 강제로 검사 합니다.

참고 : 이전에는이 ​​답변이 Underscore.js를 권장 했지만 lodash 는 버그를 수정하고 일관성있는 문제를 해결하는 더 나은 작업을 수행했습니다.


27
Underscore의 isEqual 함수는 매우 훌륭합니다 (그러나 라이브러리를 사용하려면 약 3K의 gzipped를 사용해야합니다).
mckoss

29
다른 밑줄이 제공하는 것을 보면 후회하지 않을 것입니다.
PandaWood

6
종속성으로 밑줄을 쓸 여유가 없어도 isEqual 함수를 꺼내고 라이센스 요구 사항을 충족시킨 다음 계속하십시오. 스택 오버 플로우에서 언급 된 가장 포괄적 인 평등 테스트입니다.
데일 앤더슨

7
LoDash 라는 Underscore 포크가 있으며 그 저자는 이와 같은 일관성 문제에 매우 관심이 있습니다. LoDash로 테스트하고 얻을 수있는 것을 확인하십시오.
CoolAJ86

6
@mckoss 전체 라이브러리 npmjs.com/package/lodash.isequal을
Rob Fox

161

JavaScript for Objects의 기본 동등 연산자는 메모리에서 동일한 위치를 참조 할 때 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 () 함수가 단순 해집니다.
scotts

3
@scotts 항상 그런 것은 아닙니다. 객체를 JSON으로 변환하고 문자열을 비교하면 복잡한 객체를 타이트한 루프로 계산에 집중할 수 있습니다. 단순한 물체의 경우 아마도 중요하지 않지만 실제로는 특정 상황에 따라 다릅니다. 올바른 솔루션은 객체 ID를 비교하거나 각 속성을 확인하는 것처럼 간단 할 수 있지만 정확성은 전적으로 문제 영역에 의해 결정됩니다.
Daniel X Moore

데이터 타입도 비교하지 않겠습니까?! return other.rank === this.rank && other.suit === this.suit;
devsathish

1
@devsathish는 아마 아닐 것입니다. JavaScript 유형은 매우 빠르고 느슨하지만 도메인 유형이 중요한 경우 유형을 확인하는 것이 좋습니다.
Daniel X Moore

7
@scotts JSON으로 변환 할 때의 다른 문제는 문자열의 속성 순서가 중요하다는 것입니다. {x:1, y:2}! =={y:2, x:1}
Stijn de Witt

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

이것은 내 버전입니다. ES5에 도입 된 새로운 Object.keys 기능과 + , ++의 아이디어 / 테스트를 사용 하고 있습니다 .

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
Roy Tinker

objectEquals([1,2,3],{0:1,1:2,2:3})또한 반환 true- 예를 들어, 어떤 유형 검사 만 키 / 값 확인이 없습니다.
Roy Tinker

objectEquals(new Date(1234),1234)반환true
Roy Tinker

1
if (x.constructor! == y.constructor) {return false; } 서로 다른 창에서 두 개의 'new String ('a ')'을 비교하면 문제가 발생합니다. 값이 같은지 확인하려면 두 개체 모두에서 String.isString인지 확인한 다음 느슨한 동등성 검사 'a == b'를 사용해야합니다.
Triynko

1
"가치"평등과 "엄격한"평등 사이에는 큰 차이가 있으며 같은 방식으로 구현해서는 안됩니다. 값 평등은 기본 구조를 제외하고 유형 4 : '객체'(즉, 키 / 값 쌍 모음), '숫자', '문자열'또는 '배열'중 하나 인 유형에 신경 쓰지 않아야합니다. 그게 다야. 숫자, 문자열 또는 배열이 아닌 것은 생성자가 무엇이든간에 (창간 안전) 키 / 값 쌍으로 비교해야합니다. 객체를 비교할 때 리터럴 숫자의 값과 Number의 인스턴스를 동일시하지만 문자열을 숫자로 강제 변환하지 마십시오.
Triynko

50

JSON 라이브러리를 사용하는 경우 각 객체를 JSON으로 인코딩 한 다음 결과 문자열이 동일한 지 비교할 수 있습니다.

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

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

참고 :이 답변은 많은 경우에 효과가 있지만 여러 사람들이 의견에서 지적했듯이 여러 가지 이유로 문제가 있습니다. 거의 모든 경우에보다 강력한 솔루션을 찾고 싶을 것입니다.


91
흥미롭지 만 제 생각에는 조금 까다 롭습니다. 예를 들어, 객체 속성이 항상 같은 순서로 생성된다는 것을 100 % 보증 할 수 있습니까?
귀도

25
그것은 좋은 질문이며 다른 순서로 같은 속성을 가진 두 객체가 실제로 같은지 아닌지에 대해 또 다른 질문을 제기합니다. 평등의 의미에 따라 달라집니다.
Joel Anair

11
대부분의 인코더와 문자열은 함수를 무시하고 NaN과 같은 무한 수가 아닌 숫자를 null로 변환합니다.
Stephen Belanger

4
Guido에 동의합니다. 속성 순서는 중요하며 보장 할 수 없습니다. @JoelAnair, 속성 값이 동일한 경우 다른 순서로 동일한 속성을 가진 두 개의 객체가 동일한 것으로 간주되어야한다고 생각합니다.
Juzer Ali

5
이것은 객체 키를 일관되게 정렬하는 대체 JSON stringifier와 함께 작동 할 수 있습니다.
Roy Tinker

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

5
당신은 대체 할 수 reduce와 함께 every간단합니다.
jib

1
@nonkertompf 그가 할 수 있는지 확인하십시오 : Object.keys(x).every(key => deepEqual(x[key], y[key])).
jib

2
두 날짜를 비교할 때 실패합니다
Greg

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()수 있으며 그렇지 않으면 사용자 정의 기능이 도움이 될 수 있습니다.


긴 방법이지만 각 객체의 속성 순서에 대한 가정없이 객체를 완전히 테스트합니다.
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
브라우저 / 엔진에서 주문 보존이 보장되지 않는다고 생각합니다.
Jo Liss

@JoLiss Citations needed;) 여러 브라우저에서 이것을 테스트하면서 일관된 결과를 얻었습니다. 그러나 물론 미래의 브라우저 / 엔진에서 동작이 동일하게 유지되도록 보장 할 수있는 사람은 없습니다. 이것은 트릭입니다 (답변에서 이미 언급했듯이). 객체를 비교하는 확실한 방법이라는 의미는 아닙니다.
Ates Goral

1
물론, 여기 몇 가지 조언이 있습니다 : ECMAScript 사양에 따르면 object는 "정렬되지 않았습니다" ; 그리고 이 답변 현재 브라우저에서 실제 발산 동작합니다.
Jo Liss

2
@JoLiss 감사합니다! 그러나 코드와 컴파일 된 객체 간의 순서 유지를 주장하지 않았다는 점에 유의하십시오. 값이 대체되는 속성의 순서를 유지한다고 주장했습니다. 이것이 내 솔루션의 핵심이었습니다. 믹스 인을 사용하여 속성 값을 덮어 쓰는 것입니다. 구현에서는 일반적으로 일종의 해시 맵을 사용하기로 결정하고 값만 바꾸면 키 순서가 유지됩니다. 실제로 다른 브라우저에서 테스트 한 것은 정확히 이것입니다.
Ates Goral

1
@AtesGoral :이 제약을 좀 더 명확 하게 만들 수 있습니까 (boldface, ...). 대부분의 사람들은 간단하게 할 복사 - 붙여 넣기 ... 주위의 텍스트를 읽지 않고
윌렘 반 Onsem에게

21

Node.js에서는 native를 사용할 수 있습니다 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 2016 년

Node.js의 일부 버전이로 설정 error.name되었습니다 "AssertionError [ERR_ASSERTION]". 이 경우 if 문을로 바꿉니다 if (error.code === 'ERR_ASSERTION') {.
Knute Knudsen

deepStrictEqual갈 길은 몰랐습니다 . 나는 왜 strictEqual작동하지 않는지 알아 내려고 뇌를 깨고 있었다. 환상적인.
NetOperator Wibby

19

Object, Array, String, Int 등 모든 것을 비교할 수있는 가장 간단 하고 논리적 인 솔루션

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

노트 :

  • 당신은 교체해야 val1하고 val2개체로
  • 객체의 경우 양쪽 객체에 대해 키별로 재귀 적으로 정렬해야합니다.

6
JSON.stringify알파벳 순서 가 바뀌지 않는 한 객체의 키 순서가 중요하지 않기 때문에 많은 경우에 이것이 작동하지 않는다고 가정합니다 . (어떤 찾을 수없는 기록 .)
브람 Vanroy에게

yup 당신 말이 맞아 ... 개체에 대해, 당신은 양쪽 개체에 대해 재귀 적으로 정렬해야합니다
Pratik Bhalodiya

2
이것은 순환 참조와 개체에 대해 작동하지 않습니다
네이트 비트 지능

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

데모는 여기에서 가능합니다


개체 키의 순서가 변경된 경우 작동하지 않습니다.
Isaac Pak

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의 각 소품을 거치기 전에이 검사를 추가하십시오 if (Object.getOwnPropertyNames (a) .length! == Object.getOwnPropertyNames (b) .length) false 반환
Hith

1
적절한 동등성 검사기는 재귀 적이어야합니다. 그런 재귀 답변 중 하나가 정답이어야한다고 생각합니다. 허용 된 답변은 코드를 제공하지 않으며 도움이되지 않습니다
canbax

9

NodeJS를 사용하는 사용자에게는이를 isDeepStrictEqual달성 할 수있는 기본 유틸리티 라이브러리에서 편리한 메소드 가 있습니다.

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는 어쨌든 문자열로 만들고 자바 스크립트에서 문자열을 비교하는 것은 큰 문제가 아닙니다
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,이 이상 기능하지 않습니다. 다른 답변을 확인하고 객체를 통해 루프를 사용하십시오.
Adriano Spadoni

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
이것은 OP가 원하는 것을 수행하지 않습니다. 두 객체가 모두 동일한 키를 가지고 있고 그렇지 않은 것으로 나타납니다. 또한 키가 동일한 순서로되어 있어야하므로 실제로는 합리적이지 않습니다.
SpeedOfRound

1
속성이 다른 순서로 무엇입니까 ??? 아니 안 좋은 방법
이씨 Sakaria

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 ). 다음은 "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
아, 나도 잊어 버렸지 만 동일한 객체 인 경우 먼저 객체 균등화 및 맹렬한 객체를 먼저 테스트하여 기능을 가속화 할 수 있습니다. if (obj1 === obj2) return true;
th317erd

11
areEqual({a: 'b'}, {b: 'a'})도착 true후?
okm

예,이 "솔루션"에 문제가 있음을 게시 한 후 깨달았습니다. 실제로 제대로 작동하려면 정렬 알고리즘에서 약간 더 많은 작업이 필요합니다.
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
나는 이것의 변형을 사용했다. 회원 수를 세는 아이디어에 감사드립니다!
궁둥이

2
Object.prototype대부분의 경우 권장하지 않는 수정에 매우주의하십시오 ( 예 : 추가는 모든 for..in 루프에 나타남). 아마도 고려 Object.equals = function(aObj, bObj) {...}할까요?
Roy Tinker

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

나는 같은 문제에 직면하여 내 자신의 솔루션을 작성하기로 결정했다. 그러나 Arrays와 Objects도 비교하고 싶기 때문에 일반적인 솔루션을 만들었습니다. 프로토 타입에 함수를 추가하기로 결정했지만 독립형 함수로 쉽게 다시 작성할 수 있습니다. 코드는 다음과 같습니다.

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 함수 자체와 배열 / 객체에서 속성의 숫자 인덱스를 찾는 함수입니다. findof 함수는 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과 다릅니다.


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 솔루션이 제안하는 것처럼 해싱이나 직렬화에 대해 조언합니다. 두 객체가 동일한 지 테스트해야하는 경우 동일한 것이 무엇인지 정의해야합니다. 두 개체의 모든 데이터 멤버가 일치하거나 메모리 위치가 일치해야하거나 (두 변수가 모두 메모리의 동일한 개체를 참조 함) 각 개체의 데이터 멤버가 하나만 일치해야 할 수 있습니다.

최근에 인스턴스를 만들 때마다 생성자가 새 ID를 생성하고 (1에서 시작하여 1 씩 증가) 객체를 개발했습니다. 이 객체에는 해당 id 값을 다른 객체의 id 값과 비교하고 일치하면 true를 반환하는 isEqual 함수가 있습니다.

이 경우 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 포함) : Unit Test and Test Runner here .


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 인 경우 해당 속성 값과 해당 유형의 동일성 (3 중 동등성)을 비교합니다.

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

여기에서이를 복제하고 다른 배열 집합을 만들어 값과 키를 비교할 수 있습니다. 그것들은 이제 배열이기 때문에 매우 간단하며 객체의 크기가 다른 경우 false를 반환합니다.


1
false배열은 값으로 비교되지 않기 때문에 항상을 반환 합니다 (예 :) [1,2] != [1,2].
jib
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.