32

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

2つの配列またはオブジェクトがあり、それらを比較したい場合(次のように)

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

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

object1 == object2 // false

サーバーから応答を受け取り、それが変更されたかどうかを確認しようとすると、これは煩わしい場合があります

4

2 に答える 2

33

更新:
元の提案 (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、単に関数を無視します(まあ、それらをすべて一緒に削除します)。機能ではなく、データを表すことを意図しています。 関数のみを含む 2 つのオブジェクトを比較しようとすると、次のようになります。
true

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

オブジェクト/関数を詳細に比較するには、ライブラリを参照するか、独自の関数を記述し、JS オブジェクトがすべて参照であるという事実を克服する必要があります。したがって、比較するo1 === ob2と、両方の変数が同じを指している場合にのみ true が返されます。物体...

@ajがコメントで指摘したように:

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

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

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

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

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

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

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

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

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

于 2012-10-30T16:04:04.897 に答える
3

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

コーヒースクリプトで:

_.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;
  }
});
于 2012-10-30T15:59:37.460 に答える