2つのJavaScriptオブジェクトの等価性を判断する方法は?


669

厳密な等価演算子は、2つのオブジェクトタイプが等しいかどうかを通知します。しかし、Javaのハッシュコード値のように、 2つのオブジェクトが等しいかどうかを確認する方法はありますか?

スタックオーバーフローの質問JavaScriptには、何らかのhashCode関数がありますか?この質問に似ていますが、より学術的な答えが必要です。上記のシナリオは、なぜそれが必要なのかを示しており、同等の解決策があるかどうか疑問に思っています


3
また、この質問を確認してください。stackoverflow.com
q / 1068834/1671639

37
Javaであっても、がと等しいことを意味するa.hashCode() == b.hashCode()わけではないことに注意してください。それは必要条件であり、十分条件ではありません。ab
ハインツィ2014


2
コード内のオブジェクトを比較しなければならない場合は、おそらくコードを間違って記述しているでしょう。より良い質問は、「オブジェクトを比較する必要がないように、このコードをどのように書くことができますか?」
th317erd 2017

3
@ th317erdあなた自身について説明していただけますか?...
El Mac

回答:


175

短い答え

簡単な答えは次のとおりです。いいえ、ある意味でオブジェクトが別のオブジェクトと等しいと判断する一般的な方法はありません。例外は、オブジェクトが型なしであると厳密に考えている場合です。

長い答え

概念は、オブジェクトの2つの異なるインスタンスを比較して、値レベルで等しいかどうかを示すEqualsメソッドの概念です。ただし、Equalsメソッドの実装方法を定義するのは特定のタイプ次第です。プリミティブ値を持つ属性の反復比較では不十分な場合があります。オブジェクト値の一部と見なされない属性がある場合もあります。例えば、

 function MyClass(a, b)
 {
     var c;
     this.getCLazy = function() {
         if (c === undefined) c = a * b // imagine * is really expensive
         return c;
     }
  }

このような場合では、cMyClassの任意の2つのインスタンスのみが、等しいかどうかを決定するために本当に重要ではなく、aかつb重要です。場合cによっては、インスタンス間で異なる場合がありますが、比較中は重要ではありません。

この問題は、メンバー自体も型のインスタンスである可能性があり、これらすべてが等しいかどうかを判断する手段を持つ必要がある場合に適用されます。

さらに複雑なことは、JavaScriptではデータとメソッドの区別が不明瞭になることです。

オブジェクトは、イベントハンドラーとして呼び出されるメソッドを参照する場合があり、これはおそらく「値の状態」の一部とは見なされません。一方、別のオブジェクトには、重要な計算を実行する関数が割り当てられている場合があり、それによって、別の関数を参照しているという理由だけで、このインスタンスは他のオブジェクトと異なります。

既存のプロトタイプメソッドの1つが別の関数によってオーバーライドされているオブジェクトはどうですか?それでも、他の場合と同じである別のインスタンスと等しいと見なされますか?その質問は、各タイプの特定のケースでのみ回答できます。

前に述べたように、例外は厳密に型なしのオブジェクトです。その場合、唯一の賢明な選択は、各メンバーの反復的および再帰的な比較です。それでも、関数の「値」とは何かを尋ねなければなりませんか?


174
あなたはアンダースコアを使用している場合は、あなただけ行うことができます_.isEqual(obj1, obj2);
chovy

12
@過酷です。答えがないため、答えはありませんでした。Javaでも、オブジェクトの等価比較に特効薬はなく、.equalsメソッドを正しく実装することは簡単ではありません。そのため、Effective Javaに特化したトピックが存在します
lcn

3
@Kumar Harsh、2つのオブジェクトを等しくするものはアプリケーション固有です。オブジェクトのすべてのプロパティを必ずしも考慮する必要があるわけではないため、オブジェクトのすべてのプロパティを総当たりにすることも、具体的な解決策ではありません。
sethro 2014年

googled javascript equality object、tl; drの返信を得て、@ chovyコメントから1行を取得しました。ありがとう
Andrea

Angularを使用している場合angular.equals
ボートコーダーは2014

506

なぜ車輪を再発明するのですか?与えるLodashに試して。isEqual()など、いくつかの必須機能があります。

_.isEqual(object, other);

ブラウザで利用可能な場合は、ECMAScript 5とネイティブ最適化を使用して、このページの他の例と同様に、各キー値を総当たりチェックします。

注:以前はこの回答がUnderscore.jsを推奨していましたが、lodashはバグを修正し、一貫性のある問題に対処するというより良い仕事をしました。


27
UnderscoreのisEqual関数は非常に優れています(ただし、使用するにはライブラリーをプルする必要があります-約3K gzip圧縮)。
mckoss 2010

29
他にアンダースコアが何を提供しているかを見れば、それを引き込んだことを後悔することはありません
PandaWood

6
依存関係としてアンダースコアを使用する余裕がない場合でも、isEqual関数を引き出して、ライセンス要件を満たし、次に進みます。これは、stackoverflowで言及された最も包括的な同等性テストです。
デールアンダーソン、

7
LoDashと呼ばれるアンダースコアの分岐があり、その作者はそのような一貫性の問題に非常に関心があります。LoDashでテストして、何が得られるかを確認してください。
CoolAJ86 2013

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()関数が単純になります。
スコットは

3
@scotts常にではありません。オブジェクトをJSONに変換して文字列を比較すると、タイトループの複雑なオブジェクトでは計算負荷が高くなります。単純なオブジェクトの場合、それはおそらくそれほど重要ではありませんが、実際には特定の状況に本当に依存します。正しい解決策は、オブジェクトIDの比較や各プロパティのチェックと同じくらい簡単かもしれませんが、その正確さは問題ドメインによって完全に決定されます。
ダニエルXムーア

データ型も比較してはいけませんか?!return other.rank === this.rank && other.suit === this.suit;
devsathish 2013年

1
@devsathishはおそらくそうではありません。JavaScriptではタイプはかなり高速でルーズですが、ドメイン内でタイプが重要な場合は、タイプもチェックすることをお勧めします。
Daniel X Moore

7
@scotts JSONへの変換に関するその他の問題は、文字列内のプロパティの順序が重要になることです。{x:1, y:2}!=={y:2, x:1}
Stijn de Witt 2016

81

AngularJSで作業している場合、angular.equals関数は2つのオブジェクトが等しいかどうかを判断します。では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

これは私のバージョンです。これは、新しい使用しているObject.keysの ES5とアイデアで導入された機能を/テストから+++

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たとえば、型チェックはなく、キー/値チェックのみです。
ロイ・ティンカー

objectEquals(new Date(1234),1234)リターンtrue
ロイティンカー

1
if(x.constructor!== y.constructor){return false; これは、異なるウィンドウで2つの 'new String(' a ')'を比較すると壊れます。値の等価性については、両方のオブジェクトでString.isStringかどうかを確認してから、緩やかな等価性チェック 'a == b'を使用する必要があります。
Triynko

1
「値」の等価性と「厳密な」等価性の間には大きな違いがあり、それらは同じ方法で実装されるべきではありません。値の等価性は、これらの4つのうちの1つである基本構造を除いて、タイプを気にする必要はありません。それでおしまい。数値、文字列、または配列ではないものはすべて、コンストラクターが何であるかに関係なく(クロスウィンドウセーフ)、キーと値のペアのセットとして比較する必要があります。オブジェクトを比較するときは、リテラルの数値とNumberのインスタンスを同等に扱いますが、文字列を数値に強制しないでください。
Triynko

50

JSONライブラリを使用している場合は、各オブジェクトをJSONとしてエンコードし、結果の文字列が等しいかどうかを比較できます。

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

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

注:この回答は多くの場合有効ですが、コメントで指摘したように、さまざまな理由で問題があります。ほとんどすべての場合、より堅牢なソリューションを見つける必要があります。


91
興味深いですが、私の意見では少しトリッキーです。たとえば、オブジェクトプロパティが常に同じ順序で生成されることを100%保証できますか?
グイド

25
これは良い質問であり、同じプロパティを持つ異なる順序の2つのオブジェクトが実際に等しいかどうかに関して別の問題を提起します。あなたが平等にどういう意味かによると思います。
Joel Anair、2008年

11
ほとんどのエンコーダーと文字列化関数は関数を無視し、NaNなどの非有限数をnullに変換することに注意してください。
スティーブンベランジャー2011

4
私はグイドに同意します。プロパティの順序は重要であり、保証することはできません。@JoelAnair、私はプロパティの値が等しい場合、異なる順序で同じプロパティを持つ2つのオブジェクトは等しいと見なすべきだと思います。
Juzer Ali

5
これ、オブジェクトキーを一貫してソートする代替のJSON文字列指定子と連携して機能します。
ロイティンカー

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

5
あなたは置き換えることができreduceevery簡素化します。
jib 2016年

1
@nonkertompf彼ができることを確認してください:Object.keys(x).every(key => deepEqual(x[key], y[key]))
ジブ

2
これは、2つの日付を比較していると失敗します
Greg

3
deepEqual({}、[])はtrueを返します
AlexMorley-Finch 2018

3
はい、そのようなコーナーケースを気にする場合、醜い解決策は次のものに置き換える: (x === y)ことです: (x === y && (x != null && y != null || x.constructor === y.constructor))
atmin

22

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()関数が適しています。


これは長い方法ですが、各オブジェクトのプロパティの順序を想定せずにオブジェクトを完全にテストします。
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/

根拠:

のプロパティはobj11つずつクローンにコピーされるため、クローン内でのそれらの順序は保持されます。のプロパティがobj2クローンにコピーされると、すでに存在するプロパティobj1は単純に上書きされるため、クローン内のそれらの順序は保持されます。


11
ブラウザー/エンジン全体で順序の保存が保証されているとは思いません。
Jo Liss

@JoLiss引用が必要です;)複数のブラウザでこれをテストして、一貫した結果が得られたことを思い出します。しかし、もちろん、将来のブラウザ/エンジンで同じ動作を維持することを誰も保証できません。これはせいぜい(回答ですでに説明されているように)トリックであり、オブジェクトを比較するための確実な方法であるとは言いませんでした。
Ates Goral 2013年

1
確かに、ここでいくつかのポインタです:ECMAScriptの仕様では、オブジェクトが「順不同」であると言います。そして、この答えは、現在のブラウザ上での実際の分岐動作のために。
Jo Liss

2
@JoLissありがとうございます!ただし、コードとコンパイル済みオブジェクトの間の順序の保持を主張したことはありません。値がインプレースで置き換えられるプロパティの順序の保持を主張していました。それが私のソリューションの鍵でした。ミックスインを使用してプロパティ値を上書きすることです。実装が一般にある種のハッシュマップを使用することを選択すると仮定すると、値だけを置き換えるとキーの順序が保持されます。実際、私が別のブラウザでテストしたのはまさにこれです。
Ates Goral 2013年

1
@AtesGoral:この制約をもう少し明示的にすることは可能ですか(太字など)。ほとんどの人は、周囲のテキストを読まずに単にコピーして貼り付けます...
Willem Van Onsem

21

Node.jsでは、そのネイティブを使用できます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

Node.jsの一部のバージョンはに設定されerror.nameてい"AssertionError [ERR_ASSERTION]"ます。この場合、ifステートメントをに置き換えif (error.code === 'ERR_ASSERTION') {ます。
クヌートクヌーセン2018年

私は考えが持っていたdeepStrictEqual進むべき道ではなかったです。なぜ機能しstrictEqualなかったのかを理解しようとして頭を悩ませていました。素晴らしい。
NetOperator Wibby

19

オブジェクト、配列、文​​字列、整数などすべてを比較するための最も単純論理的なソリューション...

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

注意:

  • あなたは交換する必要があるval1val2あなたのオブジェクトに
  • オブジェクトの場合、両方のサイドオブジェクトに対して(キーで)再帰的にソートする必要があります

6
オブジェクトのキーの順序は関係ないので、これは多くの場合機能しないと思います- JSON.stringifyアルファベット順の並べ替えをしない限り?(私は文書化されたものを見つけることができません。)
Bram Vanroy 2017

そうです、オブジェクトについては、両方のサイドオブジェクトについて再帰的にソートする必要があります
Pratik Bhalodiya '29

2
これは循環参照を持つオブジェクトでは機能しません
Nate-Bit Int

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)return false
Hith

1
適切な等価チェッカーが再帰的でなければならないことは明らかです。そのような再帰的な答えの1つが正しい答えであると思います。受け入れられた答えはコードを与えず、それも役に立たない
canbax

9

NodeJSを使用している人のisDeepStrictEqualために、これを実現できるネイティブUtilライブラリで呼び出される便利なメソッドがあります。

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はとにかく文字列を作成し、JavaScriptの文字列の比較は大したことではありません
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に感謝します。変更されたキー/属性を取得する方法を知っていますか?よろしく
お願いし

1
こんにちは@digital、どのキーが異なる必要がある場合、これは理想的な機能ではありません。他の答えを確認し、オブジェクトをループする1つを使用してください。
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 2018年

1
プロパティの順序はどのようになっていますか??? いいえ、良い方法ではありません
Vishal 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(''));
}

このメソッドのもう1つの便利な点は、JSON.stringify関数(「https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON」に「replacer」関数を渡すことで比較をフィルタリングできることです。 / 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
ああ、私も忘れてしまいましたが、同じオブジェクトである場合、最初にオブジェクトの等化とベイルを最初にテストすることで関数を高速化できます。
th317erd

11
areEqual({a: 'b'}, {b: 'a'})true次に取得しますか?
okm 2015

ええ、私は投稿した後、この「解決策」には問題があることに気付きました。実際に正しく動作するには、ソートアルゴリズムでもう少し作業が必要です。
th317erd 2015

4

いくつかのes6機能を利用したオブジェクト比較のバージョンを提供したかっただけです。注文は考慮されません。すべてのif / elseをternaryに変換した後、私は次のようになりました:

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
私はこれのバリエーションを使用してしまいました。メンバーを数えるという考えをありがとう!
NateS 2010

2
変更には細心の注意を払ってください。Object.prototype大部分の場合、これは推奨されません(たとえば、すべてのfor..inループに追加が表示されます)。たぶん考慮Object.equals = function(aObj, bObj) {...}
ロイティンカー

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

2つのオブジェクトが異なる場合、MongoDB互換の{$rename:{...}, $unset:{...}, $set:{...}}likeオブジェクトが返されます。


3

私は同じ問題に直面し、私自身の解決策を書くことにしました。しかし、配列とオブジェクトを比較したり、その逆もしたいので、汎用的なソリューションを作成しました。プロトタイプに関数を追加することにしましたが、スタンドアロン関数に簡単に書き直すことができます。これがコードです:

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を返します。注意すべき1つの細部:このコードは、2つの変数の同じタイプもチェックするため、「3」は3と同じではありません。


3

スパゲッティコードの回答が表示されます。サードパーティのライブラリを使用しなくても、これは非常に簡単です。

まず、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)

これもディープコピーでは機能せず、反復の深さは1つだけです。
アンドラス、

@andrasはネストされたオブジェクトのキーを再帰的にソートする必要があることを意味すると思います。
Davi Lima、

2

(JSONソリューションが示唆するように)ハッシュやシリアル化はお勧めしません。2つのオブジェクトが等しいかどうかをテストする必要がある場合は、等しいという意味を定義する必要があります。両方のオブジェクトのすべてのデータメンバーが一致するか、メモリ位置が一致する必要がある(両方の変数がメモリ内の同じオブジェクトを参照する)か、各オブジェクトの1つのデータメンバーのみが一致する必要がある可能性があります。

最近、インスタンスが作成されるたびにコンストラクターが新しいID(1から始まり、1ずつ増加する)を作成するオブジェクトを開発しました。このオブジェクトには、そのID値を別のオブジェクトのID値と比較し、一致する場合にtrueを返すisEqual関数があります。

その場合、id値が一致することを意味する「等しい」を定義しました。各インスタンスに一意のIDがある場合、これを使用して、一致するオブジェクトも同じメモリロケーションを占有するという考えを適用できます。それは必要ではありませんが。


2

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を含む): 単体テストテストランナーはこちら


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を返すため、非常に簡単です。


false配列は値によって比較されないため、これは常にを返します[1,2] != [1,2]
ジブ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.