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

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

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



답변

짧은 대답

간단한 대답은 다음과 같습니다. 아니요, 의미하는 의미에서 객체가 다른 객체와 같은지를 결정하는 일반적인 방법은 없습니다. 개체가 유형이 없다고 엄격하게 생각할 때는 예외입니다.

긴 대답

개념은 객체의 두 개의 서로 다른 인스턴스를 비교하여 값 수준에서 동일한 지 여부를 나타내는 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에서 데이터와 메소드의 구별이 흐려진다는 것입니다.

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

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

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


답변

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

_.isEqual(object, other);

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

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


답변

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


답변

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>


답변

이것은 내 버전입니다. 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 } }));


답변

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

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

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

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


답변

짧은 기능 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);
}