etc./StackOverFlow

JavaScript의 객체 비교 [중복]

청렴결백한 만능 재주꾼 2023. 4. 24. 06:47
반응형

질문자 :spankmaster79


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


_proto_ 재귀적으로 사용하고 열거할 수 없는 모든 속성에 액세스하지 않는 한 완벽한 방법은 없지만 이것은 Firefox에서만 작동합니다.

따라서 내가 할 수 있는 최선은 사용 시나리오를 추측하는 것입니다.


1) 빠르고 제한적입니다.

내부에 메서드와 DOM 노드가 없는 간단한 JSON 스타일 객체가 있을 때 작동합니다.

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

알려진 문제(우선순위가 매우 낮기 때문에 아마 눈치채지 못할 것입니다):

  • 프로토타입 구조는 다르지만 투영은 동일한 객체
  • 함수는 동일한 텍스트를 가질 수 있지만 다른 클로저를 참조할 수 있습니다.

테스트: 통과 테스트는 두 JavaScript 객체의 동등성을 결정하는 방법에서 가져왔습니다. .


crazyx

다음은 내 ES3 주석 솔루션입니다(코드 뒤의 유혈 정보).

 function object_equals( 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는 null 속성과 정의되지 않은 속성을 모두 허용하며 객체에는 확인하지 않으면 매우 다른 동작으로 이어질 수 있는 프로토타입 체인이 있습니다.

먼저 저는 Object.prototype 을 확장하지 않기로 결정했습니다. 그 이유는 주로 null 이 비교 대상 중 하나가 될 수 없고 null 이 다른 것과 비교할 유효한 개체여야 한다고 믿기 때문입니다. 다른 사람의 코드에 대한 가능한 부작용과 관련하여 Object.prototype 의 확장과 관련하여 다른 사람들이 지적한 다른 합법적인 우려도 있습니다.

자바 스크립트가 정의되지 않은에 개체 속성, 즉 값이 undefined로 설정되는 속성이 존재 설정할 수 있습니다 허용하는 특별한주의가 가능성을 처리하기 위해 촬영해야합니다. 위의 솔루션은 두 개체가 동일성을 보고 하기 위해 정의되지 않음으로 설정된 동일한 속성이 있는지 확인합니다. 이것은 Object.hasOwnProperty( property_name ) 를 사용하여 속성의 존재를 확인해야만 달성할 수 있습니다. 또한 JSON.stringify() 는 undefined 로 설정된 속성을 제거하므로 이 형식을 사용한 비교는 undefined 값으로 설정된 속성을 무시합니다.

동일한 코드가 아니라 동일한 참조를 공유하는 경우에만 함수가 동일한 것으로 간주되어야 합니다. 이는 이러한 함수 프로토타입을 고려하지 않기 때문입니다. 따라서 코드 문자열을 비교한다고 해서 동일한 프로토타입 객체가 있다는 보장이 되지는 않습니다.

두 객체는 동일한 속성뿐만 아니라 동일한 프로토타입 체인을 가져야 합니다. 이것은 엄격한 평등을 위해 두 객체 의 생성자 를 비교함으로써만 크로스 브라우저를 테스트할 수 있습니다. ECMAScript 5는 Object.getPrototypeOf()를 사용하여 실제 프로토타입을 테스트할 수 있습니다. 일부 웹 브라우저는 동일한 작업을 수행 하는 __proto__ 속성도 제공합니다. 위 코드를 개선하면 가능할 때마다 이러한 방법 중 하나를 사용할 수 있습니다.

2 가 "2.0000" 과 같거나 falsenull , undefined 또는 0 과 같은 것으로 간주되어서는 안 되므로 엄격한 비교를 사용하는 것이 여기에서 가장 중요합니다.

효율성 고려 사항으로 인해 가능한 한 빨리 속성의 평등을 비교할 수 있습니다. 그런 다음, 그 실패하는 경우에만 이러한 속성 대해서 typeof를 찾습니다. 속도 향상은 스칼라 속성이 많은 큰 개체에서 중요할 수 있습니다.

더 이상 두 개의 루프가 필요하지 않습니다. 첫 번째는 왼쪽 개체에서 속성을 확인하고 두 번째는 오른쪽에서 속성을 확인하고 값이 아닌 존재만 확인하여 정의되지 않은 값으로 정의된 이러한 속성을 잡습니다.

전반적으로 이 코드는 주석 없이 16줄의 코드로 대부분의 코너 케이스를 처리합니다.


업데이트(2015년 8월 13일) . 더 빠른 함수 value_equals() 가 -0이 아닌 NaN 및 0과 같은 모서리 케이스를 적절하게 처리하고 선택적으로 객체의 속성 순서를 적용하고 100개 이상의 자동화된 테스트 가 지원하는 순환 참조에 대한 테스트를 수행하므로 더 나은 버전을 구현했습니다. Toubkal 프로젝트 테스트 스위트의 일부로.


Jean Vincent

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

ONE-LEVEL 전용 개체를 비교하는 간단한 방법입니다.


pincopallo

확실히 유일한 방법은 아닙니다. 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()을 사용하여 메서드를 테스트하는 것은 절대적으로 충분하지 않지만 허용되는 메서드는 의미가 있는 공백의 문제로 인해 매우 어렵습니다. 동의어 메서드와 메서드가 다른 구현으로 동일한 결과를 생성하는 것은 신경쓰지 마십시오. 그리고 일반적으로 Object에 대한 프로토타이핑의 문제.


annakata

다음 알고리즘은 자체 참조 데이터 구조, 숫자, 문자열, 날짜 및 일반 중첩 자바스크립트 객체를 처리합니다.

객체는 다음과 같은 경우 동등한 것으로 간주됩니다.

  • === 대해 정확히 동일 42 Number(42) 와 동일한지 확인하기 위해 String 및 Number가 먼저 언래핑됨)
  • 또는 둘 다 날짜이고 동일한 valueOf()
  • 또는 둘 다 같은 유형이고 null이 아니며 ...
    • 그것들은 객체가 아니며 == 따라 동일합니다(숫자/문자열/부울 잡기)
    • undefined 값이 있는 속성을 무시하면 동일한 속성이 모두 재귀적으로 동일한 것으로 간주됩니다.

기능 은 기능 텍스트에 의해 동일한 것으로 간주되지 않습니다. 함수의 클로저가 다를 수 있으므로 이 테스트는 충분하지 않습니다. === 가 그렇게 말하는 경우에만 동일한 것으로 간주됩니다(그러나 그렇게 하기로 선택하면 해당 관계를 쉽게 확장할 수 있습니다).

순환 데이터 구조로 인해 발생할 수 있는 무한 루프를 방지합니다. areEquivalent 가 평등을 반증하려고 시도하고 그렇게 하기 위해 객체의 속성으로 재귀할 때 이 하위 비교가 필요한 객체를 추적합니다. 평등이 반증될 수 있다면 어떤 도달 가능한 속성 경로는 객체 사이에 다르며 가장 짧은 도달 가능한 경로가 있어야 하며 가장 짧은 도달 가능한 경로는 두 경로에 있는 사이클을 포함할 수 없습니다. 즉, 객체를 재귀적으로 비교할 때 동등하다고 가정하는 것이 좋습니다. areEquivalent_Eq_91_2_34 속성에 저장되지만 개체 그래프에 이미 이러한 속성이 포함되어 있으면 동작이 정의되지 않습니다. 자바스크립트는 임의의 객체를 키로 사용하는 사전을 지원하지 않기 때문에 이러한 마커 속성을 사용해야 합니다.

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

Eamon Nerbonne

객체 비교를 위해 이 코드를 작성했는데 작동하는 것 같습니다. 주장을 확인하십시오:

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

mhoms

위의 코드를 약간 수정했습니다. 나를 위해 0 !== falsenull !== undefined . 그러한 엄격한 검사가 필요하지 않다면 코드 내에서 " this[p] !== x[p] " 로그인 하나를 " = " 제거하십시오.

 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 예상 거짓; 거짓을 반환

c==d 예상 거짓; 거짓을 반환

a==예상 거짓; 거짓을 반환

f==g 예상 참; true를 반환

h==g 예상되는 거짓; 거짓을 반환

i==j 예상 참; true를 반환

d==k 예상 거짓; 거짓을 반환

k==나는 거짓이 예상됨; 거짓을 반환


Jevgeni Kiski

여기 내 버전이 있습니다. 이 스레드의 거의 모든 것이 통합되었습니다(테스트 사례에 대해 동일한 수).

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

여기 내 테스트 케이스가 있습니다:

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

gossi

메서드를 명시적으로 확인하려면 method.toSource() 또는 method.toString() 메서드를 사용할 수 있습니다.


Nicolas R

JSON 라이브러리 없이 작업하는 경우 다음이 도움이 될 수 있습니다.

 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

Samuel Weber

출처 : http:www.stackoverflow.com/questions/1068834/object-comparison-in-javascript

반응형