web-dev-qa-db-ja.com

オブジェクト/配列の詳細な比較

可能性のある複製:
2つのJavaScriptオブジェクトの同等性をどのように判断しますか?
JavaScriptでのオブジェクト比較

2つの配列またはオブジェクトがあり、それらを比較したい場合、例えば

object1 = [
 { shoes:
   [ 'loafer', 'penny' ]
  },
  { beers:
     [ 'budweiser', 'busch' ]
  }
]

object2 = [
 { shoes:
   [ 'loafer', 'penny' ]
  },
  { beers:
     [ 'budweiser', 'busch' ]
  }
]

object1 == object2 // false

これは、サーバーから応答を得て、それが変更されているかどうかを確認しようとする場合、迷惑になる可能性があります

19
Funkodebat

更新:
元の提案(2つのJSON文字列を比較)を取り巻くコメントと心配に応えて、次の関数を使用できます。

_function compareObjects(o, p)
{
    var i,
        keysO = Object.keys(o).sort(),
        keysP = Object.keys(p).sort();
    if (keysO.length !== keysP.length)
        return false;//not the same nr of keys
    if (keysO.join('') !== keysP.join(''))
        return false;//different keys
    for (i=0;i<keysO.length;++i)
    {
        if (o[keysO[i]] instanceof Array)
        {
            if (!(p[keysO[i]] instanceof Array))
                return false;
            //if (compareObjects(o[keysO[i]], p[keysO[i]] === false) return false
            //would work, too, and perhaps is a better fit, still, this is easy, too
            if (p[keysO[i]].sort().join('') !== o[keysO[i]].sort().join(''))
                return false;
        }
        else if (o[keysO[i]] instanceof Date)
        {
            if (!(p[keysO[i]] instanceof Date))
                return false;
            if ((''+o[keysO[i]]) !== (''+p[keysO[i]]))
                return false;
        }
        else if (o[keysO[i]] instanceof Function)
        {
            if (!(p[keysO[i]] instanceof Function))
                return false;
            //ignore functions, or check them regardless?
        }
        else if (o[keysO[i]] instanceof Object)
        {
            if (!(p[keysO[i]] instanceof Object))
                return false;
            if (o[keysO[i]] === o)
            {//self reference?
                if (p[keysO[i]] !== p)
                    return false;
            }
            else if (compareObjects(o[keysO[i]], p[keysO[i]]) === false)
                return false;//WARNING: does not deal with circular refs other than ^^
        }
        if (o[keysO[i]] !== p[keysO[i]])//change !== to != for loose comparison
            return false;//not the same value
    }
    return true;
}
_

しかし、多くの場合、それほど難しいIMOである必要はありません。

_JSON.stringify(object1) === JSON.stringify(object2);
_

文字列化されたオブジェクトが同じ場合、それらの値は同じです。
完全を期すために、JSONは単に関数を無視します(まあ、それらをすべて一緒に削除します)。これは、機能性ではなく、Dataを表すことを意図しています。
関数のみを含む2つのオブジェクトを比較しようとすると、trueになります。

_JSON.stringify({foo: function(){return 1;}}) === JSON.stringify({foo: function(){ return -1;}});
//evaulutes to:
'{}' === '{}'
//is true, of course
_

オブジェクト/関数の詳細な比較を行うには、libsを使用するか、独自の関数を作成し、JSオブジェクトがすべて参照であるという事実を克服する必要があります。したがって、_o1 === ob2_を比較すると、変数は同じオブジェクトを指します...

@ a-jがコメントで指摘したように:

_JSON.stringify({a: 1, b: 2}) === JSON.stringify({b: 2, a: 1});
_

両方のstringify呼び出しがそれぞれ_"{"a":1,"b":2}"_と_"{"b":2,"a":1}"_を生成するため、falseです。その理由については、ChromeのV8エンジンの内部を理解する必要があります。私は専門家ではありません。詳しく説明しなくても、次のように要約できます。

作成される各オブジェクト、および変更されるたびに、V8は新しい非表示C++クラス(の一種)を作成します。オブジェクトXにプロパティaがあり、別のオブジェクトに同じプロパティがある場合、これらのJSオブジェクトは両方とも、このプロパティaを定義する共有非表示クラスから継承する非表示クラスを参照します。 2つのオブジェクトがすべて同じ基本プロパティを共有する場合、それらはすべて同じ非表示クラスを参照し、_JSON.stringify_は両方のオブジェクトでまったく同じように機能します。それは与えられたものです(V8の内部の詳細 here 、もし興味があれば)。

ただし、a-jで指摘されている例では、両方のオブジェクトが異なる方法で文字列化されています。どうして?簡単に言うと、これらのオブジェクトは同時に存在することはありません。

_JSON.stringify({a: 1, b: 2})
_

これは関数呼び出しです。これは、右側のオペランドと比較する前に、結果の値に解決する必要がある式です。 2番目のオブジェクトリテラルはまだテーブルにありません。
オブジェクトは文字列化され、exoressionは文字列定数に解決されます。オブジェクトリテラルはどこでも参照されておらず、ガベージコレクションのフラグが立てられています。
この後、右側のオペランド(JSON.stringify({b: 2, a: 1})式)は同じ扱いになります。

すべてうまくいきますが、考慮する必要があるのは、JSエンジンが以前よりもはるかに洗練されていることです。繰り返しますが、私はV8の専門家ではありませんが、コードが次のように最適化されているという点で、a-jのスニペットが大幅に最適化されていると考えられます。

_"{"b":2,"a":1}" === "{"a":1,"b":2}"
_

基本的に_JSON.stringify_呼び出しをすべて省略し、適切な場所に引用符を追加するだけです。つまり、結局のところ、はるかに効率的です。

27

アンダースコアミックスインとして:

コーヒースクリプトで:

_.mixin deepEquals: (ar1, ar2) ->

    # typeofs should match
    return false unless (_.isArray(ar1) and _.isArray(ar2)) or (_.isObject(ar1) and _.isObject(ar2))

    #lengths should match
    return false if ar1.length != ar2.length

    still_matches = true

    _fail = -> still_matches = false

    _.each ar1, (prop1, n) =>

      prop2 = ar2[n]

      return if prop1 == prop2

      _fail() unless _.deepEquals prop1, prop2

    return still_matches

そして、javascriptで:

_.mixin({
  deepEquals: function(ar1, ar2) {
    var still_matches, _fail,
      _this = this;
    if (!((_.isArray(ar1) && _.isArray(ar2)) || (_.isObject(ar1) && _.isObject(ar2)))) {
      return false;
    }
    if (ar1.length !== ar2.length) {
      return false;
    }
    still_matches = true;
    _fail = function() {
      still_matches = false;
    };
    _.each(ar1, function(prop1, n) {
      var prop2;
      prop2 = ar2[n];
      if (prop1 !== prop2 && !_.deepEquals(prop1, prop2)) {
        _fail();
      }
    });
    return still_matches;
  }
});
3
Funkodebat