1334

JavaScript でオブジェクトを比較する最良の方法は何ですか?

例:

var user1 = {name : "nerd", org: "dev"};
var user2 = {name : "nerd", org: "dev"};
var eq = user1 == user2;
alert(eq); // gives false

2 つのオブジェクトがまったく同じ object を参照している場合、それらが等しいことはわかっていますが、それらが同じ属性の値を持っているかどうかを確認する方法はありますか?

次の方法は私にとってはうまくいきますが、それが唯一の可能性ですか?

var eq = Object.toJSON(user1) == Object.toJSON(user2);
alert(eq); // gives true
4

10 に答える 10

1594

残念ながら、再帰的に使用してすべての列挙不可能なプロパティにアクセスしない限り、完全な方法はありません_proto_が、これは Firefox でのみ機能します。

したがって、私にできる最善のことは、使用シナリオを推測することです。


1) 高速で限定的。

内部にメソッドと DOM ノードのない単純な JSON スタイルのオブジェクトがある場合に機能します。

 JSON.stringify(obj1) === JSON.stringify(obj2) 

プロパティの順序は重要であるため、このメソッドは次のオブジェクトに対して false を返します。

 x = {a: 1, b: 2};
 y = {b: 2, a: 1};

2) 遅く、より一般的です。

プロトタイプを掘り下げずにオブジェクトを比較し、プロパティのプロジェクションを再帰的に比較し、コンストラクターも比較します。

これはほぼ正しいアルゴリズムです:

function deepCompare () {
  var i, l, leftChain, rightChain;

  function compare2Objects (x, y) {
    var p;

    // remember that NaN === NaN returns false
    // and isNaN(undefined) returns true
    if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
         return true;
    }

    // Compare primitives and functions.     
    // Check if both arguments link to the same object.
    // Especially useful on the step where we compare prototypes
    if (x === y) {
        return true;
    }

    // Works in case when functions are created in constructor.
    // Comparing dates is a common scenario. Another built-ins?
    // We can even handle functions passed across iframes
    if ((typeof x === 'function' && typeof y === 'function') ||
       (x instanceof Date && y instanceof Date) ||
       (x instanceof RegExp && y instanceof RegExp) ||
       (x instanceof String && y instanceof String) ||
       (x instanceof Number && y instanceof Number)) {
        return x.toString() === y.toString();
    }

    // At last checking prototypes as good as we can
    if (!(x instanceof Object && y instanceof Object)) {
        return false;
    }

    if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
        return false;
    }

    if (x.constructor !== y.constructor) {
        return false;
    }

    if (x.prototype !== y.prototype) {
        return false;
    }

    // Check for infinitive linking loops
    if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
         return false;
    }

    // Quick checking of one object being a subset of another.
    // todo: cache the structure of arguments[0] for performance
    for (p in y) {
        if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
            return false;
        }
        else if (typeof y[p] !== typeof x[p]) {
            return false;
        }
    }

    for (p in x) {
        if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
            return false;
        }
        else if (typeof y[p] !== typeof x[p]) {
            return false;
        }

        switch (typeof (x[p])) {
            case 'object':
            case 'function':

                leftChain.push(x);
                rightChain.push(y);

                if (!compare2Objects (x[p], y[p])) {
                    return false;
                }

                leftChain.pop();
                rightChain.pop();
                break;

            default:
                if (x[p] !== y[p]) {
                    return false;
                }
                break;
        }
    }

    return true;
  }

  if (arguments.length < 1) {
    return true; //Die silently? Don't know how to handle such case, please help...
    // throw "Need two or more arguments to compare";
  }

  for (i = 1, l = arguments.length; i < l; i++) {

      leftChain = []; //Todo: this can be cached
      rightChain = [];

      if (!compare2Objects(arguments[0], arguments[i])) {
          return false;
      }
  }

  return true;
}

既知の問題 (まあ、それらの優先度は非常に低く、おそらく気付かないでしょう):

  • プロトタイプ構造は異なるが投影は同じオブジェクト
  • 関数は同じテキストを持っているかもしれませんが、異なるクロージャーを参照しています

テスト:合格のテストはHow to determine equality for two JavaScript objects?からのものです。.

于 2009-07-17T16:08:59.473 に答える
215

Here is my ES3 commented solution (gory details after the code):

function object_equals( x, y ) {
  if ( x === y ) return true;
    // if both x and y are null or undefined and exactly the same

  if ( ! ( x instanceof Object ) || ! ( y instanceof Object ) ) return false;
    // if they are not strictly equal, they both need to be Objects

  if ( x.constructor !== y.constructor ) return false;
    // they must have the exact same prototype chain, the closest we can do is
    // test there constructor.

  for ( var p in x ) {
    if ( ! x.hasOwnProperty( p ) ) continue;
      // other properties were tested using x.constructor === y.constructor

    if ( ! y.hasOwnProperty( p ) ) return false;
      // allows to compare x[ p ] and y[ p ] when set to undefined

    if ( x[ p ] === y[ p ] ) continue;
      // if they have the same strict value or identity then they are equal

    if ( typeof( x[ p ] ) !== "object" ) return false;
      // Numbers, Strings, Functions, Booleans must be strictly equal

    if ( ! object_equals( x[ p ],  y[ p ] ) ) return false;
      // Objects and Arrays must be tested recursively
  }

  for ( p in y )
    if ( y.hasOwnProperty( p ) && ! x.hasOwnProperty( p ) )
      return false;
        // allows x[ p ] to be set to undefined

  return true;
}

In developing this solution, I took a particular look at corner cases, efficiency, yet trying to yield a simple solution that works, hopefully with some elegance. JavaScript allows both null and undefined properties and objects have prototypes chains that can lead to very different behaviors if not checked.

First I have chosen to not extend Object.prototype, mostly because null could not be one of the objects of the comparison and that I believe that null should be a valid object to compare with another. There are also other legitimate concerns noted by others regarding the extension of Object.prototype regarding possible side effects on other's code.

Special care must taken to deal the possibility that JavaScript allows object properties can be set to undefined, i.e. there exists properties which values are set to undefined. The above solution verifies that both objects have the same properties set to undefined to report equality. This can only be accomplished by checking the existence of properties using Object.hasOwnProperty( property_name ). Also note that JSON.stringify() removes properties that are set to undefined, and that therefore comparisons using this form will ignore properties set to the value undefined.

Functions should be considered equal only if they share the same reference, not just the same code, because this would not take into account these functions prototype. So comparing the code string does not work to guaranty that they have the same prototype object.

The two objects should have the same prototype chain, not just the same properties. This can only be tested cross-browser by comparing the constructor of both objects for strict equality. ECMAScript 5 would allow to test their actual prototype using Object.getPrototypeOf(). Some web browsers also offer a __proto__ property that does the same thing. A possible improvement of the above code would allow to use one of these methods whenever available.

The use of strict comparisons is paramount here because 2 should not be considered equal to "2.0000", nor false should be considered equal to null, undefined, or 0.

Efficiency considerations lead me to compare for equality of properties as soon as possible. Then, only if that failed, look for the typeof these properties. The speed boost could be significant on large objects with lots of scalar properties.

No more that two loops are required, the first to check properties from the left object, the second to check properties from the right and verify only existence (not value), to catch these properties which are defined with the undefined value.

Overall this code handles most corner cases in only 16 lines of code (without comments).


Update (8/13/2015). I have implemented a better version, as the function value_equals() that is faster, handles properly corner cases such as NaN and 0 different than -0, optionally enforcing objects' properties order and testing for cyclic references, backed by more than 100 automated tests as part of the Toubkal project test suite.

于 2011-07-15T22:21:43.713 に答える
50
  Utils.compareObjects = function(o1, o2){
    for(var p in o1){
        if(o1.hasOwnProperty(p)){
            if(o1[p] !== o2[p]){
                return false;
            }
        }
    }
    for(var p in o2){
        if(o2.hasOwnProperty(p)){
            if(o1[p] !== o2[p]){
                return false;
            }
        }
    }
    return true;
};

ONE-LEVEL のみのオブジェクトを比較する簡単な方法。

于 2011-05-02T15:27:20.207 に答える
23

確かに唯一の方法ではありません - C#/Java スタイルの比較メソッドを複製するために、(ここでは Object に対してですが、ライブ コードに Object を使用することはお勧めしません) メソッドのプロトタイプを作成できます。

一般的な例が期待されるように見えるので、編集してください:

Object.prototype.equals = function(x)
{
    for(p in this)
    {
        switch(typeof(this[p]))
        {
            case 'object':
                if (!this[p].equals(x[p])) { return false }; break;
            case 'function':
                if (typeof(x[p])=='undefined' || (p != 'equals' && this[p].toString() != x[p].toString())) { return false; }; break;
            default:
                if (this[p] != x[p]) { return false; }
        }
    }

    for(p in x)
    {
        if(typeof(this[p])=='undefined') {return false;}
    }

    return true;
}

toString() を使用したメソッドのテストは絶対に十分ではありませんが、空白に意味があるかどうかの問題があるため、受け入れられるメソッドは非常に難しいことに注意してください。同義のメソッドや、異なる実装で同じ結果を生成するメソッドは気にしないでください。そして、一般的なオブジェクトに対するプロトタイピングの問題。

于 2009-07-01T12:25:59.613 に答える
19

次のアルゴリズムは、自己参照型のデータ構造、数値、文字列、日付、およびもちろんプレーンなネストされた JavaScript オブジェクトを処理します。

次の場合、オブジェクトは同等と見なされます。

  • それらは完全に等しいです(文字列と数値は、と同等であること===を確認するために最初にアンラップされます)42Number(42)
  • または両方とも日付であり、同じvalueOf()
  • または、それらは両方とも同じ型で、null ではなく...
    • それらはオブジェクトではなく、等しい==(数値/文字列/ブール値をキャッチ)
    • または、値を持つプロパティを無視するとundefined、同じプロパティがすべて再帰的に同等と見なされます。

関数は、関数テキストによって同一とは見なされません。関数のクロージャが異なる可能性があるため、このテストは不十分です。関数は、そのよう===に言われた場合にのみ等しいと見なされます (ただし、そうすることを選択した場合、その同等の関係を簡単に拡張できます)。

循環データ構造によって引き起こされる可能性のある無限ループが回避されます。areEquivalent等しいことを反証しようとし、そのためにオブジェクトのプロパティに再帰する場合、このサブ比較が必要なオブジェクトを追跡します。等しいことが反証できる場合、到達可能なプロパティ パスの一部がオブジェクト間で異なり、そのような到達可能な最短パスが存在する必要があり、到達可能な最短パスに両方のパスに存在するサイクルを含めることはできません。つまり、オブジェクトを再帰的に比較するときに等しいと仮定しても問題ありません。仮定はプロパティに保存されますareEquivalent_Eq_91_2_34、使用後に削除されますが、オブジェクト グラフにそのようなプロパティが既に含まれている場合、動作は未定義です。javascript は任意のオブジェクトをキーとして使用する辞書をサポートしていないため、このようなマーカー プロパティを使用する必要があります。

function unwrapStringOrNumber(obj) {
    return (obj instanceof Number || obj instanceof String 
            ? obj.valueOf() 
            : obj);
}
function areEquivalent(a, b) {
    a = unwrapStringOrNumber(a);
    b = unwrapStringOrNumber(b);
    if (a === b) return true; //e.g. a and b both null
    if (a === null || b === null || typeof (a) !== typeof (b)) return false;
    if (a instanceof Date) 
        return b instanceof Date && a.valueOf() === b.valueOf();
    if (typeof (a) !== "object") 
        return a == b; //for boolean, number, string, xml

    var newA = (a.areEquivalent_Eq_91_2_34 === undefined),
        newB = (b.areEquivalent_Eq_91_2_34 === undefined);
    try {
        if (newA) a.areEquivalent_Eq_91_2_34 = [];
        else if (a.areEquivalent_Eq_91_2_34.some(
            function (other) { return other === b; })) return true;
        if (newB) b.areEquivalent_Eq_91_2_34 = [];
        else if (b.areEquivalent_Eq_91_2_34.some(
            function (other) { return other === a; })) return true;
        a.areEquivalent_Eq_91_2_34.push(b);
        b.areEquivalent_Eq_91_2_34.push(a);

        var tmp = {};
        for (var prop in a) 
            if(prop != "areEquivalent_Eq_91_2_34") 
                tmp[prop] = null;
        for (var prop in b) 
            if (prop != "areEquivalent_Eq_91_2_34") 
                tmp[prop] = null;

        for (var prop in tmp) 
            if (!areEquivalent(a[prop], b[prop]))
                return false;
        return true;
    } finally {
        if (newA) delete a.areEquivalent_Eq_91_2_34;
        if (newB) delete b.areEquivalent_Eq_91_2_34;
    }
}
于 2011-06-19T14:29:39.517 に答える
14

オブジェクト比較のためにこのコードを書きましたが、うまくいくようです。アサーションを確認します。


function countProps(obj) {
    var count = 0;
    for (k in obj) {
        if (obj.hasOwnProperty(k)) {
            count++;
        }
    }
    return count;
};

function objectEquals(v1, v2) {

    if (typeof(v1) !== typeof(v2)) {
        return false;
    }

    if (typeof(v1) === "function") {
        return v1.toString() === v2.toString();
    }

    if (v1 instanceof Object && v2 instanceof Object) {
        if (countProps(v1) !== countProps(v2)) {
            return false;
        }
        var r = true;
        for (k in v1) {
            r = objectEquals(v1[k], v2[k]);
            if (!r) {
                return false;
            }
        }
        return true;
    } else {
        return v1 === v2;
    }
}

assert.isTrue(objectEquals(null,null));
assert.isFalse(objectEquals(null,undefined));

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

assert.isTrue(objectEquals(function(x){return x;},function(x){return x;}));
assert.isFalse(objectEquals(function(x){return x;},function(y){return y+2;}));
于 2010-10-03T11:01:30.250 に答える
6

上記のコードを少し修正しました。私にとっては0 !== falsenull !== undefinedです。このような厳密なチェックが必要ない場合は、コード内の" = " 記号 " this[p] !== x[p] " を 1 つ削除してください。

Object.prototype.equals = function(x){
    for (var p in this) {
        if(typeof(this[p]) !== typeof(x[p])) return false;
        if((this[p]===null) !== (x[p]===null)) return false;
        switch (typeof(this[p])) {
            case 'undefined':
                if (typeof(x[p]) != 'undefined') return false;
                break;
            case 'object':
                if(this[p]!==null && x[p]!==null && (this[p].constructor.toString() !== x[p].constructor.toString() || !this[p].equals(x[p]))) return false;
                break;
            case 'function':
                if (p != 'equals' && this[p].toString() != x[p].toString()) return false;
                break;
            default:
                if (this[p] !== x[p]) return false;
        }
    }
    return true;
}

次に、次のオブジェクトでテストしました。

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 f = {a: 'text', b:[1,0], f: function(){ this.f = this.b; }};
var g = {a: 'text', b:[1,0], f: function(){ this.f = this.b; }};
var h = {a: 'text', b:[1,0], f: function(){ this.a = this.b; }};
var i = {
    a: 'text',
    c: {
        b: [1, 0],
        f: function(){
            this.a = this.b;
        }
    }
};
var j = {
    a: 'text',
    c: {
        b: [1, 0],
        f: function(){
            this.a = this.b;
        }
    }
};
var k = {a: 'text', b: null};
var l = {a: 'text', b: undefined};

a==b 期待される真; 真を返した

a==c 予想される false; false を返しました

c==d 予想される false; false を返しました

a==e 予想される false; false を返しました

f==g 期待される真; 真を返した

h==g 予想される false; false を返しました

i==j 期待される真; 真を返した

d==k 予想される false; false を返しました

k==l 予想される false; false を返しました

于 2010-04-29T09:17:50.767 に答える
2

メソッドを明示的にチェックする場合は、method.toSource()またはmethod.toString()メソッドを使用できます。

于 2009-07-01T15:44:47.917 に答える
2

JSON ライブラリを使用せずに作業する場合は、次の方法が役立つかもしれません。

Object.prototype.equals = function(b) {
    var a = this;
    for(i in a) {
        if(typeof b[i] == 'undefined') {
            return false;
        }
        if(typeof b[i] == 'object') {
            if(!b[i].equals(a[i])) {
                return false;
            }
        }
        if(b[i] != a[i]) {
            return false;
        }
    }
    for(i in b) {
        if(typeof a[i] == 'undefined') {
            return false;
        }
        if(typeof a[i] == 'object') {
            if(!a[i].equals(b[i])) {
                return false;
            }
        }
        if(a[i] != b[i]) {
            return false;
        }
    }
    return true;
}

var a = {foo:'bar', bar: {blub:'bla'}};
var b = {foo:'bar', bar: {blub:'blob'}};
alert(a.equals(b)); // alert's a false
于 2010-03-09T10:38:14.353 に答える