2つのJavaScriptオブジェクトの同等性を判断する方法は?
-
03-07-2019 - |
質問
厳密な等価演算子は、2つのオブジェクト types が等しいかどうかを示します。ただし、Javaのハッシュコードの値のような 2つのオブジェクトが等しいかどうかを確認する方法はありますか?
スタックオーバーフローの質問 JavaScriptにhashCode関数はありますか? はこの質問に似ています、しかしよりアカデミックな答えが必要です。上記のシナリオは、それが必要な理由を示しており、同等のソリューションがあるかどうか疑問に思っています。
解決
短い答え
簡単な答えは次のとおりです。いいえ、あなたが意味する意味でオブジェクトが別のものと等しいことを決定する一般的な手段はありません。例外は、オブジェクトが型なしであると厳密に考えている場合です。
長い答え
概念は、オブジェクトの2つの異なるインスタンスを比較して、値レベルで等しいかどうかを示すEqualsメソッドの概念です。ただし、 Equals
メソッドの実装方法を定義するのは特定のタイプ次第です。プリミティブ値を持つ属性の反復比較では不十分な場合があり、オブジェクト値の一部と見なされない属性がある場合があります。たとえば、
function MyClass(a, b)
{
var c;
this.getCLazy = function() {
if (c === undefined) c = a * b // imagine * is really expensive
return c;
}
}
この場合、MyClassの2つのインスタンスが等しいかどうかを判断するために c
はあまり重要ではなく、 a
と b
のみが等しい重要。場合によっては、 c
はインスタンス間で異なる場合がありますが、比較中は重要ではありません。
この問題は、メンバー自身も型のインスタンスであり、それぞれが平等を判断する手段を持つ必要がある場合に適用されることに注意してください。
さらに複雑なことは、JavaScriptではデータとメソッドの区別があいまいになることです。
オブジェクトは、イベントハンドラとして呼び出されるメソッドを参照できますが、これは「値の状態」の一部とは見なされない可能性があります。一方、別のオブジェクトには、重要な計算を実行する関数が割り当てられる可能性があり、それにより、異なるインスタンスを参照するという理由だけで、このインスタンスを他のインスタンスとは異なるものにします。
既存のプロトタイプメソッドの1つが別の関数によってオーバーライドされているオブジェクトはどうですか?それが他の点では同一であるということは、他のインスタンスとまだ等しいと考えられますか?その質問は、各タイプの特定のケースでのみ回答できます。
前述のとおり、例外は厳密に型のないオブジェクトです。その場合、唯一の賢明な選択は、各メンバーの反復的かつ再帰的な比較です。それでも、関数の「値」とは何かを尋ねる必要がありますか?
他のヒント
なぜ車輪を再発明するのですか? Lodash を試してください。 isEqual()などの必須機能が多数あります。
_.isEqual(object, other);
このページの他の例と同様に、各キー値を総当たりでチェックします。 ECMAScript&nbspを使用します; 5 およびネイティブ最適化(ブラウザーで使用可能な場合)。
注:以前は、この回答では Underscore.js が推奨されていましたが、 lodash は、バグを修正し、一貫性のある問題に対処するというより良い仕事をしました。
オブジェクトの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
AngularJS で作業している場合、 angular.equals
関数は2つのオブジェクトは等しいです。 Ember.js で isEqual
を使用します。
-
angular.equals
-ドキュメントまたは<このメソッドの詳細については、a href = "https://github.com/angular/angular.js/blob/6c59e770084912d2345e7f83f983092a2d305ae3/src/Angular.js#L670" rel = "noreferrer"> source をご覧ください。配列も深く比較します。 - Ember.js
isEqual
-ドキュメントまたは source このメソッドの詳細。配列の詳細な比較は行いません。
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>
これは私のバージョンです。新しい 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);
}
ディープコピー機能が便利な場合は、次のトリックを使用して、プロパティの順序を一致させながら 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
にすでに存在するプロパティは単純に上書きされるため、クローン内の順序は保持されます。
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:&quot; bar&quot;}}
)しかし、あなたはアイデアを得る。
指摘したように、あなたはあなた自身の目的のためにこれを適応させなければならないかもしれません。例えば:異なるクラスは異なる「等しい」の定義を持っているかもしれません。単純なオブジェクトで作業している場合は上記で十分です。そうでない場合は、カスタムの MyClass.equals()
関数を使用する方法があります。
最も単純なおよび論理的なソリューション
JSON.stringify({a:val1})=== JSON.stringify({a:val2})
注:
-
val1
とval2
をオブジェクトに置き換える必要があります - オブジェクトの場合は、両側のオブジェクトについて再帰的に(キーで)ソートする必要があります
Node.jsでは、ネイティブの require(&quot; assert&quot;)。deepEqual
を使用できます。詳細:
http://nodejs.org/api/assert.html
例:
var assert = require("assert");
assert.deepEqual({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;
};
この 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;
}
}
これに似たものが投稿されているかどうかはわかりませんが、オブジェクトの同等性をチェックするために作成した関数は次のとおりです。
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;
}
また、再帰的であるため、深い平等性を確認することもできます。
underscore.jsライブラリの _。isEqual(obj1、obj2)
を使用できます。
例を次に示します。
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
多くの人が気付いていないこの問題の簡単な解決策は、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(''));
}
このメソッドのもう1つの便利な点は、「置換」を渡すことで比較をフィルタリングできることです。 JSON.stringify関数への関数( https:// developer。 mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Example_of_using_replacer_parameter )。以下では、&quot; derp&quot;という名前のすべてのオブジェクトキーのみを比較します。
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;
});
(JSONソリューションが示唆するように)ハッシュ化またはシリアル化を避けることをお勧めします。 2つのオブジェクトが等しいかどうかをテストする必要がある場合は、等しいという意味を定義する必要があります。両方のオブジェクトのすべてのデータメンバーが一致するか、メモリの場所が一致する必要がある(両方の変数がメモリ内の同じオブジェクトを参照する)か、各オブジェクトの1つのデータメンバーのみが一致する必要がある可能性があります。
最近、インスタンスが作成されるたびにコンストラクターが新しいID(1から始まり1ずつ増加する)を作成するオブジェクトを開発しました。このオブジェクトには、そのid値を別のオブジェクトのid値と比較し、一致する場合にtrueを返すisEqual関数があります。
その場合、「等しい」を定義しました。 ID値が一致することを意味します。各インスタンスに一意のIDがあるため、これを使用して、一致するオブジェクトも同じメモリロケーションを占有するという考え方を実施できます。必須ではありませんが。
投稿されたものよりも汎用的なオブジェクト比較関数が必要なため、次のように作り上げました。批評に感謝...
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は数値と文字列のみを検索し、オブジェクトは検索しないため、検索機能のみが必要です。
次のように呼び出すことができます:
({a: 1, b: "h"}).equals({a: 1, b: "h"});
この関数はtrueまたはfalseを返します。この場合はtrueです。 アルゴリズムalsは、非常に複雑なオブジェクト間の比較を可能にします。
({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とは異なります。
いくつかの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;
}
)
}
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を含む): 単体テストおよびここでテストランナー。
この関数を使用して次のことを想定しています:
- 比較するオブジェクトを制御し、プリミティブ値のみを持ちます(つまり、ネストされたオブジェクト、関数などではありません)。
- ブラウザは 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;
}
これは上記のすべての追加であり、代替ではありません。余分な再帰的なケースをチェックする必要なく、オブジェクトを浅く比較する必要がある場合。これがショットです。
これは、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;
}
単純なキー/値ペアのオブジェクトインスタンスのキーを比較するには、次を使用します。
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個を超えるプロパティを保持しないことを望みます...
これは少し古いことは知っていますが、この問題に対して思いついた解決策を追加したいと思います。 オブジェクトがあり、そのデータがいつ変更されたかを知りたかった。 &quot; Object.observeに似たもの&quot;そして私がやったことは:
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を返すため、非常に簡単です。
自分の仕事に繰り返し使用している個人ライブラリから引き出します。次の関数は寛容な再帰的ディープイコールであり、チェックしません
- クラスの平等
- 継承された値
- 値の厳密な平等
主にこれを使用して、さまざまなAPI実装に対して同等の応答が返されるかどうかを確認します。実装の違い(文字列と数値など)および追加のnull値が発生する可能性があります。
その実装は非常に簡単で短いです(すべてのコメントが削除される場合)
/** Recursively check if both objects are equal in value
***
*** This function is designed to use multiple methods from most probable
*** (and in most cases) valid, to the more regid and complex method.
***
*** One of the main principles behind the various check is that while
*** some of the simpler checks such as == or JSON may cause false negatives,
*** they do not cause false positives. As such they can be safely run first.
***
*** # !Important Note:
*** as this function is designed for simplified deep equal checks it is not designed
*** for the following
***
*** - Class equality, (ClassA().a = 1) maybe valid to (ClassB().b = 1)
*** - Inherited values, this actually ignores them
*** - Values being strictly equal, "1" is equal to 1 (see the basic equality check on this)
*** - Performance across all cases. This is designed for high performance on the
*** most probable cases of == / JSON equality. Consider bench testing, if you have
*** more 'complex' requirments
***
*** @param objA : First object to compare
*** @param objB : 2nd object to compare
*** @param .... : Any other objects to compare
***
*** @returns true if all equals, or false if invalid
***
*** @license Copyright by eugene@picoded.com, 2012.
*** Licensed under the MIT license: http://opensource.org/licenses/MIT
**/
function simpleRecusiveDeepEqual(objA, objB) {
// Multiple comparision check
//--------------------------------------------
var args = Array.prototype.slice.call(arguments);
if(args.length > 2) {
for(var a=1; a<args.length; ++a) {
if(!simpleRecusiveDeepEqual(args[a-1], args[a])) {
return false;
}
}
return true;
} else if(args.length < 2) {
throw "simpleRecusiveDeepEqual, requires atleast 2 arguments";
}
// basic equality check,
//--------------------------------------------
// if this succed the 2 basic values is equal,
// such as numbers and string.
//
// or its actually the same object pointer. Bam
//
// Note that if string and number strictly equal is required
// change the equality from ==, to ===
//
if(objA == objB) {
return true;
}
// If a value is a bsic type, and failed above. This fails
var basicTypes = ["boolean", "number", "string"];
if( basicTypes.indexOf(typeof objA) >= 0 || basicTypes.indexOf(typeof objB) >= 0 ) {
return false;
}
// JSON equality check,
//--------------------------------------------
// this can fail, if the JSON stringify the objects in the wrong order
// for example the following may fail, due to different string order:
//
// JSON.stringify( {a:1, b:2} ) == JSON.stringify( {b:2, a:1} )
//
if(JSON.stringify(objA) == JSON.stringify(objB)) {
return true;
}
// Array equality check
//--------------------------------------------
// This is performed prior to iteration check,
// Without this check the following would have been considered valid
//
// simpleRecusiveDeepEqual( { 0:1963 }, [1963] );
//
// Note that u may remove this segment if this is what is intended
//
if( Array.isArray(objA) ) {
//objA is array, objB is not an array
if( !Array.isArray(objB) ) {
return false;
}
} else if( Array.isArray(objB) ) {
//objA is not array, objB is an array
return false;
}
// Nested values iteration
//--------------------------------------------
// Scan and iterate all the nested values, and check for non equal values recusively
//
// Note that this does not check against null equality, remove the various "!= null"
// if this is required
var i; //reuse var to iterate
// Check objA values against objB
for (i in objA) {
//Protect against inherited properties
if(objA.hasOwnProperty(i)) {
if(objB.hasOwnProperty(i)) {
// Check if deep equal is valid
if(!simpleRecusiveDeepEqual( objA[i], objB[i] )) {
return false;
}
} else if(objA[i] != null) {
//ignore null values in objA, that objB does not have
//else fails
return false;
}
}
}
// Check if objB has additional values, that objA do not, fail if so
for (i in objB) {
if(objB.hasOwnProperty(i)) {
if(objB[i] != null && !objA.hasOwnProperty(i)) {
//ignore null values in objB, that objA does not have
//else fails
return false;
}
}
}
// End of all checks
//--------------------------------------------
// By reaching here, all iteration scans have been done.
// and should have returned false if it failed
return true;
}
// Sanity checking of simpleRecusiveDeepEqual
(function() {
if(
// Basic checks
!simpleRecusiveDeepEqual({}, {}) ||
!simpleRecusiveDeepEqual([], []) ||
!simpleRecusiveDeepEqual(['a'], ['a']) ||
// Not strict checks
!simpleRecusiveDeepEqual("1", 1) ||
// Multiple objects check
!simpleRecusiveDeepEqual( { a:[1,2] }, { a:[1,2] }, { a:[1,2] } ) ||
// Ensure distinction between array and object (the following should fail)
simpleRecusiveDeepEqual( [1963], { 0:1963 } ) ||
// Null strict checks
simpleRecusiveDeepEqual( 0, null ) ||
simpleRecusiveDeepEqual( "", null ) ||
// Last "false" exists to make the various check above easy to comment in/out
false
) {
alert("FATAL ERROR: simpleRecusiveDeepEqual failed basic checks");
} else {
//added this last line, for SO snippet alert on success
alert("simpleRecusiveDeepEqual: Passed all checks, Yays!");
}
})();
文字列化のトリックのバージョンを次に示します。これは入力が少なく、JSONデータの簡単な比較の多くのケースで機能します。
var obj1Fingerprint = JSON.stringify(obj1).replace(/\{|\}/g,'').split(',').sort().join(',');
var obj2Fingerprint = JSON.stringify(obj2).replace(/\{|\}/g,'').split(',').sort().join(',');
if ( obj1Fingerprint === obj2Fingerprint) { ... } else { ... }
スパゲッティコードの回答が表示されます。 サードパーティのライブラリを使用しなくても、これは非常に簡単です。
最初に、キー名をキーとして2つのオブジェクトをソートします。
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)
NodeJSを使用している人には、これを実現できるネイティブUtilライブラリに isDeepStrictEqual
という便利なメソッドがあります。
const util = require('util');
const foo = {
hey: "ho",
lets: "go"
}
const bar = {
hey: "ho",
lets: "go"
}
foo == bar // false
util.isDeepStrictEqual(foo, bar) // true
https://nodejs.org/api/util.html#util_util_isdeepstrictequal_val1_val2