厳密な等価演算子は、2 つのオブジェクトタイプが等しいかどうかを示します。しかし、 Javaのハッシュ コード値のように、2 つのオブジェクトが等しいかどうかを判断する方法はありますか?
スタック オーバーフローの質問JavaScript には任意の種類の hashCode 関数がありますか? この質問に似ていますが、より学術的な回答が必要です。上記のシナリオは、それが必要な理由を示しており、同等のソリューションがあるかどうか疑問に思っています。
厳密な等価演算子は、2 つのオブジェクトタイプが等しいかどうかを示します。しかし、 Javaのハッシュ コード値のように、2 つのオブジェクトが等しいかどうかを判断する方法はありますか?
スタック オーバーフローの質問JavaScript には任意の種類の hashCode 関数がありますか? この質問に似ていますが、より学術的な回答が必要です。上記のシナリオは、それが必要な理由を示しており、同等のソリューションがあるかどうか疑問に思っています。
なぜ車輪を再発明するのですか?ロダッシュを試してみてください。isEqual()など、いくつかの必須関数があります。
_.isEqual(object, other);
このページの他の例と同様に、ブラウザーで使用可能な場合はECMAScript 5とネイティブの最適化を使用して、各キー値をブルート フォース チェックします。
注: 以前、この回答はUnderscore.jsを推奨していましたが、lodashはバグを修正し、一貫性を持って問題に対処するというより良い仕事をしました。
短い答え
簡単な答えは次のとおりです。いいえ、あなたが意味する意味でオブジェクトが別のものと等しいことを判断する一般的な手段はありません。例外は、オブジェクトが型なしであることを厳密に考えている場合です。
長い答え
この概念は、オブジェクトの 2 つの異なるインスタンスを比較して値レベルで等しいかどうかを示す 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 の 2 つのインスタンスが等しいかどうかを判断することはそれほど重要ではなく、重要であるだけa
ですb
。場合c
によっては、インスタンス間で異なる場合がありますが、比較時には重要ではありません。
この問題は、メンバー自体が型のインスタンスである可能性があり、これらのそれぞれが等しいかどうかを判断する手段を持つ必要がある場合に適用されることに注意してください。
さらに複雑なのは、JavaScript ではデータとメソッドの区別が曖昧なことです。
オブジェクトは、イベント ハンドラーとして呼び出されるメソッドを参照する場合がありますが、これはその「値の状態」の一部とは見なされない可能性があります。一方、別のオブジェクトには、重要な計算を実行する関数が割り当てられている可能性があり、そのため、別の関数を参照しているという理由だけで、このインスタンスを他のインスタンスと区別することができます。
既存のプロトタイプ メソッドの 1 つが別の関数によってオーバーライドされているオブジェクトについてはどうでしょうか。それ以外の場合は同一である別のインスタンスと同等であると見なすことができますか? その質問は、各タイプの特定のケースでのみ回答できます。
前述のように、例外は厳密に型のないオブジェクトになります。その場合、唯一の賢明な選択は、各メンバーの反復的かつ再帰的な比較です。それでも、関数の「値」とは何かを尋ねなければなりませんか?
オブジェクトに対する 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
これは私のバージョンです。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、jib の提案と 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);
}
For those of you using Node, there is a convenient method called isDeepStrictEqual
on the nativeutil
library that can achieve this.
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
2つのオブジェクトが等しいかどうかをテストしようとしていますか?すなわち:それらの特性は等しい?
この場合、おそらくこの状況に気付くでしょう。
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()
関数を使用することをお勧めします。
ディープ コピー機能が手元にある場合は、次のトリックを使用して、プロパティの順序を一致させながら使用できます。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
1 つずつクローンにコピーされるため、クローン内の順序は維持されます。また、 のプロパティがobj2
クローンにコピーされると、 に既に存在するプロパティobj1
は単純に上書きされるため、クローン内の順序は維持されます。
この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}}.
関数型スタイルのアプローチを使用した 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;
}
}
いくつかの es6 機能を利用した私のバージョンのオブジェクト比較に貢献したかっただけです。注文は考慮されません。すべての if/else を 3 進数に変換した後、次のようになりました。
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;
}
)
}
オブジェクト内のプロパティの順序は変更されていないと仮定します。
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));
投稿されたものよりも一般的なオブジェクト比較関数が必要なため、次のように作成しました。批評は高く評価されています...
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;
}
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 } }
2 つのオブジェクトが異なる場合は、MongoDB と互換性のある{$rename:{...}, $unset:{...}, $set:{...}}
ようなオブジェクトが返されます。
私は同じ問題に直面し、独自のソリューションを作成することにしました。しかし、配列とオブジェクト、およびその逆も比較したいので、一般的なソリューションを作成しました。関数をプロトタイプに追加することにしましたが、スタンドアロン関数に簡単に書き直すことができます。コードは次のとおりです。
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;
}
このアルゴリズムは 2 つの部分に分かれています。equals 関数自体と、配列/オブジェクト内のプロパティの数値インデックスを検索する関数。indexof は数値と文字列のみを検出し、オブジェクトは検出しないため、find 関数のみが必要です。
次のように呼び出すことができます。
({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 を返します。注意すべき小さな詳細: このコードは、2 つの変数の同じ型もチェックするため、"3" は 3 と同じではありません。
この関数では、次の仮定を行っています。
これは、単純な戦略のデモンストレーションとして扱われるべきです。
/**
* 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;
}
これは少し古いことは承知していますが、この問題に対して思いついた解決策を追加したいと思います。オブジェクトがあり、そのデータがいつ変更されたかを知りたいと思っていました。「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 を返すため、非常に簡単です。
単純なキーと値のペアのオブジェクト インスタンスのキーを比較するには、次を使用します。
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 個を超えるプロパティを保持しないことを願っています...
これは上記すべての追加であり、置き換えではありません。余分な再帰ケースをチェックする必要なく、浅い比較オブジェクトを高速化する必要がある場合。これがショットです。
これは以下を比較します: 1) 自身のプロパティの数が等しい、2) キー名が等しい、3) bCompareValues == true の場合、対応するプロパティ値とその型が等しい (三重等価)
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;
}
(JSON ソリューションが示唆するように) ハッシュまたはシリアライゼーションに反対することをお勧めします。2 つのオブジェクトが等しいかどうかをテストする必要がある場合は、equals の意味を定義する必要があります。両方のオブジェクトのすべてのデータ メンバーが一致するか、メモリ位置が一致する必要がある (つまり、両方の変数がメモリ内の同じオブジェクトを参照する) か、または各オブジェクトの 1 つのデータ メンバーのみが一致する必要がある可能性があります。
最近、インスタンスが作成されるたびにコンストラクターが新しい ID (1 から始まり、1 ずつ増加) を作成するオブジェクトを開発しました。このオブジェクトには、その id 値を別のオブジェクトの id 値と比較し、一致する場合に true を返す isEqual 関数があります。
その場合、id 値が一致することを意味するものとして「等しい」と定義しました。各インスタンスが一意の ID を持っている場合、これを使用して、一致するオブジェクトも同じメモリ位置を占有するという考えを強制することができます。それは必要ではありませんが。
2 つのオブジェクトがすべてのプロパティに対してすべて同じ値を持ち、ネストされたすべてのオブジェクトと配列に対して再帰的に同じ値を持っている場合、2 つのオブジェクトが等しいと見なすと便利です。また、次の 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 を含む): 単体テストとテスト ランナーはこちら。
これはES6 +を使用したソリューションです
// this comparison would not work for function and symbol comparisons
// this would only work best for compared objects that do not belong to same address in memory
// Returns true if there is no difference, and false otherwise
export const isObjSame = (obj1, obj2) => {
if (typeof obj1 !== "object" && obj1 !== obj2) {
return false;
}
if (typeof obj1 !== "object" && typeof obj2 !== "object" && obj1 === obj2) {
return true;
}
if (typeof obj1 === "object" && typeof obj2 === "object") {
if (Array.isArray(obj1) && Array.isArray(obj2)) {
if (obj1.length === obj2.length) {
if (obj1.length === 0) {
return true;
}
const firstElemType = typeof obj1[0];
if (typeof firstElemType !== "object") {
const confirmSameType = currentType =>
typeof currentType === firstElemType;
const checkObjOne = obj1.every(confirmSameType);
const checkObjTwo = obj2.every(confirmSameType);
if (checkObjOne && checkObjTwo) {
// they are primitves, we can therefore sort before and compare by index
// use number sort
// use alphabet sort
// use regular sort
if (firstElemType === "string") {
obj1.sort((a, b) => a.localeCompare(b));
obj2.sort((a, b) => a.localeCompare(b));
}
obj1.sort((a, b) => a - b);
obj2.sort((a, b) => a - b);
let equal = true;
obj1.map((element, index) => {
if (!isObjSame(element, obj2[index])) {
equal = false;
}
});
return equal;
}
if (
(checkObjOne && !checkObjTwo) ||
(!checkObjOne && checkObjTwo)
) {
return false;
}
if (!checkObjOne && !checkObjTwo) {
for (let i = 0; i <= obj1.length; i++) {
const compareIt = isObjSame(obj1[i], obj2[i]);
if (!compareIt) {
return false;
}
}
return true;
}
// if()
}
const newValue = isObjSame(obj1, obj2);
return newValue;
} else {
return false;
}
}
if (!Array.isArray(obj1) && !Array.isArray(obj2)) {
let equal = true;
if (obj1 && obj2) {
const allKeys1 = Array.from(Object.keys(obj1));
const allKeys2 = Array.from(Object.keys(obj2));
if (allKeys1.length === allKeys2.length) {
allKeys1.sort((a, b) => a - b);
allKeys2.sort((a, b) => a - b);
allKeys1.map((key, index) => {
if (
key.toLowerCase() !== allKeys2[index].toLowerCase()
) {
equal = false;
return;
}
const confirmEquality = isObjSame(obj1[key], obj2[key]);
if (!confirmEquality) {
equal = confirmEquality;
return;
}
});
}
}
return equal;
// return false;
}
}
};
私は Javascript の専門家ではありませんが、これを解決するための簡単な試みを 1 つ紹介します。次の 3 点をチェックします。
object
からではないnull
ことtypeof null
もありobject
ます。function deepEqual (first, second) {
// Not equal if either is not an object or is null.
if (!isObject(first) || !isObject(second) ) return false;
// If properties count is different
if (keys(first).length != keys(second).length) return false;
// Return false if any property value is different.
for(prop in first){
if (first[prop] != second[prop]) return false;
}
return true;
}
// Checks if argument is an object and is not null
function isObject(obj) {
return (typeof obj === "object" && obj != null);
}
// returns arrays of object keys
function keys (obj) {
result = [];
for(var key in obj){
result.push(key);
}
return result;
}
// Some test code
obj1 = {
name: 'Singh',
age: 20
}
obj2 = {
age: 20,
name: 'Singh'
}
obj3 = {
name: 'Kaur',
age: 19
}
console.log(deepEqual(obj1, obj2));
console.log(deepEqual(obj1, obj3));
この質問には十分な回答がありますが、1 つのアプローチが欠けています:toJSON
インターフェイスです。
通常、オブジェクトを文字列化して比較します。これが最速の方法だからです。しかし、多くの場合、プロパティの順序が原因で、比較は false と見なされます。
const obj1 = {
a: 1,
b: 2,
c: {
ca: 1,
cb: 2
}
}
const obj2 = {
b: 2, // changed order with a
a: 1,
c: {
ca: 1,
cb: 2
}
}
JSON.stringify(obj1) === JSON.stringify(obj2) // false
a
プロパティとの順序が異なるため、明らかにオブジェクトは異なると見なされb
ます。
これを解決するには、toJSON
インターフェイスを実装し、確定的な出力を定義します。
const obj1 = {
a: 1,
b: 2,
c: {
ca: 1,
cb: 2
},
toJSON() {
return {
a: this.a,
b: this.b,
c: {
ca: this.c.ca,
cb: this.c.ca
}
}
}
}
const obj2 = {
b: 2,
a: 1,
c: {
ca: 1,
cb: 2
},
toJSON() {
return {
a: this.a,
b: this.b,
c: {
ca: this.c.ca,
cb: this.c.ca
}
}
}
}
JSON.stringify(obj1) === JSON.stringify(obj2) // true
出来上がり: と の文字列表現は同じと見なされますobj1
。obj2
ヒント
オブジェクトの直接生成にアクセスできない場合は、単純にtoJSON
関数をアタッチできます。
obj1.toJSON = function() {
return {
a: this.a,
b: this.b,
c: {
ca: this.c.ca,
cb: this.c.ca
}
}
}
obj2.toJSON = function() {
return {
a: this.a,
b: this.b,
c: {
ca: this.c.ca,
cb: this.c.ca
}
}
}
JSON.stringify(obj1) === JSON.stringify(obj2) // true
配列とオブジェクトの両方が明確な方法で比較されることを確認するために、このメソッドを作成しました。
これもうまくいくはずです!:)
public class Objects {
/**
* Checks whether a value is of type Object
* @param value the value
*/
public static isObject = (value: any): boolean => {
return value === Object(value) && Object.prototype.toString.call(value) !== '[object Array]'
}
/**
* Checks whether a value is of type Array
* @param value the value
*/
public static isArray = (value: any): boolean => {
return Object.prototype.toString.call(value) === '[object Array]' && !Objects.isObject(value)
}
/**
* Check whether two values are equal
*/
public static isEqual = (objectA: any, objectB: any) => {
// Objects
if (Objects.isObject(objectA) && !Objects.isObject(objectB)) {
return false
}
else if (!Objects.isObject(objectA) && Objects.isObject(objectB)) {
return false
}
// Arrays
else if (Objects.isArray(objectA) && !Objects.isArray(objectB)) {
return false
}
else if (!Objects.isArray(objectA) && Objects.isArray(objectB)) {
return false
}
// Primitives
else if (!Objects.isArray(objectA) && !Objects.isObject(objectA)) {
return objectA === objectB
}
// Object or array
else {
const compareObject = (objectA: any, objectB: any): boolean => {
if (Object.keys(objectA).length !== Object.keys(objectB).length) return false
for (const propertyName of Object.keys(objectA)) {
const valueA = objectA[propertyName]
const valueB = objectB[propertyName]
if (!Objects.isEqual(valueA, valueB)) {
return false
}
}
return true
}
const compareArray = (arrayA: any[], arrayB: any[]): boolean => {
if (arrayA.length !== arrayB.length) return false
for (const index in arrayA) {
const valueA = arrayA[index]
const valueB = arrayB[index]
if (!Objects.isEqual(valueA, valueB)) {
return false
}
}
return true
}
if (Objects.isObject(objectA)) {
return compareObject(objectA, objectB)
} else {
return compareArray(objectA, objectB)
}
}
}
}
あなたが平等によって何を意味するかに依存します。したがって、クラスの開発者として、それらの同等性を定義するのはあなた次第です。
2つのインスタンスがメモリ内の同じ場所を指している場合、2つのインスタンスが「等しい」と見なされる場合がありますが、それが常に必要な場合とは限りません。たとえば、Personクラスがある場合、2つのPersonオブジェクトが同じ姓、名、および社会保障番号を持っていれば(メモリ内の異なる場所を指している場合でも)、「等しい」と見なすことができます。
一方、各メンバーの値が同じである場合、2つのオブジェクトが等しいと単純に言うことはできません。これは、必要でない場合があるためです。言い換えると、クラスごとに、オブジェクトの「ID」を構成するメンバーを定義し、適切な等式演算子を開発するのはクラス開発者の責任です(==演算子またはEqualsメソッドのオーバーロードを介して)。
2つのオブジェクトが同じハッシュを持っている場合、それらが等しいと言うのは1つの方法です。ただし、インスタンスごとにハッシュがどのように計算されるのか疑問に思う必要があります。上記のPersonの例に戻ると、First Name、Last Name、およびSocial Security Numberフィールドの値を調べてハッシュが計算された場合、このシステムを使用できます。その上、ハッシュメソッドの品質に依存しています(これはそれ自体が大きなトピックですが、すべてのハッシュが同じように作成されるわけではなく、ハッシュメソッドが悪いと衝突が増える可能性があります。この場合、偽の一致が返されます)。
2 つのオブジェクトが類似しているかどうかを判断する簡単な「ハック」は、toString() メソッドを使用することです。オブジェクト A と B をチェックしている場合は、A と B に意味のある toString() メソッドがあり、返される文字列が同じであることを確認してください。
これは万能薬ではありませんが、場合によっては役立つことがあります。
JSON.stringify() の使用は常に信頼できるとは限りません。したがって、この方法はあなたの質問IMOに対する最良の解決策です
まず第一に、いいえ、オブジェクトが等しいことを決定する一般的な手段はありません!
しかし、 Shallow Equality Comparisonという概念があります。この概念を使用するのに役立つnpmライブラリがあります
例
const shallowequal = require('shallowequal');
const object = { 'user': 'fred' };
const other = { 'user': 'fred' };
// Referential Equality Comparison (`strict ===`)
object === other; // → false
// Shallow Equality Comparison
shallowequal(object, other); // → true
shallowEqual
比較メソッドの作成方法については、こちらを参照してください。オープンソースのfbjs
Facebook ライブラリからのものです。
shallowequal(obj1, obj2)
shallowEqual
obj1
2 つの値 (つまりと)の間で浅い等値比較を実行してobj2
、それらが等しいかどうかを判断します。
等値化は、指定された のキーを反復処理し、 と の間で厳密に等しくない値を持つキーがあるたびobj1
に戻ることによって実行されます。それ以外の場合は、すべてのキーの値が厳密に等しい場合に戻ります。false
obj1
obj2
true
1 つの追加オプションは、 Ramda ライブラリequals
の使用です。
const c = {a: 1, b: 2};
const d = {b: 2, a: 1};
R.equals(c, d); //=> true
jQuery POST リクエストをモック化する必要があるため、重要な等価性は、両方のオブジェクトが同じプロパティ セットを持ち (どちらのオブジェクトにも欠けていない)、各プロパティ値が "等しい" (この定義によると) ことです。オブジェクトのメソッドが一致しないことは気にしません。
これが私が使用するものです。それは私の特定の要件には十分なはずです:
function PostRequest() {
for (var i = 0; i < arguments.length; i += 2) {
this[arguments[i]] = arguments[i+1];
}
var compare = function(u, v) {
if (typeof(u) != typeof(v)) {
return false;
}
var allkeys = {};
for (var i in u) {
allkeys[i] = 1;
}
for (var i in v) {
allkeys[i] = 1;
}
for (var i in allkeys) {
if (u.hasOwnProperty(i) != v.hasOwnProperty(i)) {
if ((u.hasOwnProperty(i) && typeof(u[i]) == 'function') ||
(v.hasOwnProperty(i) && typeof(v[i]) == 'function')) {
continue;
} else {
return false;
}
}
if (typeof(u[i]) != typeof(v[i])) {
return false;
}
if (typeof(u[i]) == 'object') {
if (!compare(u[i], v[i])) {
return false;
}
} else {
if (u[i] !== v[i]) {
return false;
}
}
}
return true;
};
this.equals = function(o) {
return compare(this, o);
};
return this;
}
次のように使用します。
foo = new PostRequest('text', 'hello', 'html', '<p>hello</p>');
foo.equals({ html: '<p>hello</p>', text: 'hello' });
オブジェクトの等価性チェック:JSON.stringify(array1.sort()) === JSON.stringify(array2.sort())
上記のテストはオブジェクトの配列でも機能します。この場合、http://www.w3schools.com/jsref/jsref_sort.aspに記載されているソート関数を使用します。
フラットな JSON スキーマを持つ小さな配列には十分かもしれません。
すべてのサブオブジェクトまたは配列に深く入り込む、はるかに短い関数があります。と同じくらい効率的ですJSON.stringify(obj1) === JSON.stringify(obj2)
がJSON.stringify
、順序が同じでない場合は機能しません(ここで述べたように)。
var obj1 = { a : 1, b : 2 };
var obj2 = { b : 2, a : 1 };
console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // false
この関数は、等しくない値で何かをしたい場合にも適しています。
function arr_or_obj(v)
{ return !!v && (v.constructor === Object || v.constructor === Array); }
function deep_equal(v1, v2)
{
if (arr_or_obj(v1) && arr_or_obj(v2) && v1.constructor === v2.constructor)
{
if (Object.keys(v1).length === Object.keys(v2).length) // check the length
for (var i in v1)
{
if (!deep_equal(v1[i], v2[i]))
{ return false; }
}
else
{ return false; }
}
else if (v1 !== v2)
{ return false; }
return true;
}
//////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////
var obj1 = [
{
hat : {
cap : ['something', null ],
helmet : [ 'triple eight', 'pro-tec' ]
},
shoes : [ 'loafer', 'penny' ]
},
{
beers : [ 'budweiser', 'busch' ],
wines : [ 'barefoot', 'yellow tail' ]
}
];
var obj2 = [
{
shoes : [ 'loafer', 'penny' ], // same even if the order is different
hat : {
cap : ['something', null ],
helmet : [ 'triple eight', 'pro-tec' ]
}
},
{
beers : [ 'budweiser', 'busch' ],
wines : [ 'barefoot', 'yellow tail' ]
}
];
console.log(deep_equal(obj1, obj2)); // true
console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // false
console.log(deep_equal([], [])); // true
console.log(deep_equal({}, {})); // true
console.log(deep_equal([], {})); // false
Function
、Date
およびのサポートを追加する場合は、これを(テストされていません)RegExp
の先頭に追加できます。deep_equal
if ((typeof obj1 === 'function' && typeof obj2 === 'function') ||
(obj1 instanceof Date && obj2 instanceof Date) ||
(obj1 instanceof RegExp && obj2 instanceof RegExp))
{
obj1 = obj1.toString();
obj2 = obj2.toString();
}
ええ、別の答え...
Object.prototype.equals = function (object) {
if (this.constructor !== object.constructor) return false;
if (Object.keys(this).length !== Object.keys(object).length) return false;
var obk;
for (obk in object) {
if (this[obk] !== object[obk])
return false;
}
return true;
}
var aaa = JSON.parse('{"name":"mike","tel":"1324356584"}');
var bbb = JSON.parse('{"tel":"1324356584","name":"mike"}');
var ccc = JSON.parse('{"name":"mike","tel":"584"}');
var ddd = JSON.parse('{"name":"mike","tel":"1324356584", "work":"nope"}');
$("#ab").text(aaa.equals(bbb));
$("#ba").text(bbb.equals(aaa));
$("#bc").text(bbb.equals(ccc));
$("#ad").text(aaa.equals(ddd));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
aaa equals bbb? <span id="ab"></span> <br/>
bbb equals aaa? <span id="ba"></span> <br/>
bbb equals ccc? <span id="bc"></span> <br/>
aaa equals ddd? <span id="ad"></span>
Node.js と compare.js というブラウザーで実行される小さなライブラリを作成しました。==、!=、>、>=、<、<= などの通常の比較演算子と、JavaScript のすべてのデータ型の ID を提供します。
たとえば、使用できます
cmp.eq(obj1, obj2);
これにより、等しいかどうかがチェックされます (deep-equal アプローチを使用)。それ以外の場合は、
cmp.id(obj1, obj2);
参照によって比較されるため、同一性をチェックします。オブジェクトで < と > を使用することもできます。これは、サブセットとスーパーセットを意味します。
compare.js は 700 近くの単体テストでカバーされているため、あまり多くのバグがないことを願っています ;-)。
https://github.com/goloroden/compare.jsで無料で見つけることができ、MIT ライセンスの下でオープンソース化されています。
これは古典的な JavaScript の質問です。比較から無視するプロパティを選択できる機能を備えた深いオブジェクトの等価性をチェックするメソッドを作成しました。引数は、比較する 2 つのオブジェクトと、無視する文字列化されたプロパティのオプションの配列です。
function isObjectEqual( o1, o2, ignorePropsArr=[]) {
// Deep Clone objects
let _obj1 = JSON.parse(JSON.stringify(o1)),
_obj2 = JSON.parse(JSON.stringify(o2));
// Remove props to ignore
ignorePropsArr.map( p => {
eval('_obj1.'+p+' = _obj2.'+p+' = "IGNORED"');
});
// compare as strings
let s1 = JSON.stringify(_obj1),
s2 = JSON.stringify(_obj2);
// return [s1==s2,s1,s2];
return s1==s2;
}
// Objects 0 and 1 are exact equals
obj0 = { price: 66544.10, RSIs: [0.000432334, 0.00046531], candles: {A: 543, B: 321, C: 4322}}
obj1 = { price: 66544.10, RSIs: [0.000432334, 0.00046531], candles: {A: 543, B: 321, C: 4322}}
obj2 = { price: 66544.12, RSIs: [0.000432334, 0.00046531], candles: {A: 543, B: 321, C: 4322}}
obj3 = { price: 66544.13, RSIs: [0.000432334, 0.00046531], candles: {A: 541, B: 321, C: 4322}}
obj4 = { price: 66544.14, RSIs: [0.000432334, 0.00046530], candles: {A: 543, B: 321, C: 4322}}
isObjectEqual(obj0,obj1) // true
isObjectEqual(obj0,obj2) // false
isObjectEqual(obj0,obj2,['price']) // true
isObjectEqual(obj0,obj3,['price']) // false
isObjectEqual(obj0,obj3,['price','candles.A']) // true
isObjectEqual(obj0,obj4,['price','RSIs[1]']) // true
これを行う方法のきれいなCoffeeScriptバージョンを次に示します。
Object::equals = (other) ->
typeOf = Object::toString
return false if typeOf.call(this) isnt typeOf.call(other)
return `this == other` unless typeOf.call(other) is '[object Object]' or
typeOf.call(other) is '[object Array]'
(return false unless this[key].equals other[key]) for key, value of this
(return false if typeof this[key] is 'undefined') for key of other
true
テストは次のとおりです。
describe "equals", ->
it "should consider two numbers to be equal", ->
assert 5.equals(5)
it "should consider two empty objects to be equal", ->
assert {}.equals({})
it "should consider two objects with one key to be equal", ->
assert {a: "banana"}.equals {a: "banana"}
it "should consider two objects with keys in different orders to be equal", ->
assert {a: "banana", kendall: "garrus"}.equals {kendall: "garrus", a: "banana"}
it "should consider two objects with nested objects to be equal", ->
assert {a: {fruit: "banana"}}.equals {a: {fruit: "banana"}}
it "should consider two objects with nested objects that are jumbled to be equal", ->
assert {a: {a: "banana", kendall: "garrus"}}.equals {a: {kendall: "garrus", a: "banana"}}
it "should consider two objects with arrays as values to be equal", ->
assert {a: ["apple", "banana"]}.equals {a: ["apple", "banana"]}
it "should not consider an object to be equal to null", ->
assert !({a: "banana"}.equals null)
it "should not consider two objects with different keys to be equal", ->
assert !({a: "banana"}.equals {})
it "should not consider two objects with different values to be equal", ->
assert !({a: "banana"}.equals {a: "grapefruit"})