İki JavaScript nesnesi için eşitlik nasıl belirlenir?


670

Sıkı bir eşitlik operatörü, iki nesne türünün eşit olup olmadığını söyleyecektir . Ancak, Java'daki karma kod değeri gibi iki nesnenin eşit olup olmadığını anlamanın bir yolu var mı?

Yığın Taşması sorusu JavaScript'te herhangi bir hashCode işlevi var mı? bu soruya benzer, ancak daha akademik bir cevap gerektirir. Yukarıdaki senaryo neden bir tane olması gerektiğini göstermektedir ve ben de buna eşdeğer bir çözüm olup olmadığını merak ediyorum .


3
Ayrıca bu soruya da bakın stackoverflow.com/q/1068834/1671639
Praveen

37
Java'da bile bunun eşit olduğu anlamına a.hashCode() == b.hashCode()gelmediğini unutmayın . Bu gerekli bir koşul, yeterli değil. ab
Heinzi


2
Kodunuzdaki nesneleri karşılaştırmanız gerekiyorsa, muhtemelen kodunuzu yanlış yazıyorsunuz demektir. Daha iyi bir soru olabilir: "Nesneleri karşılaştırmak zorunda kalmamak için bu kodu nasıl yazabilirim?"
th317erd

3
@ th317erd lütfen kendinizi açıklar mısınız? ...
El Mac

Yanıtlar:


175

Kısa cevap

Basit cevap şudur: Hayır, bir nesnenin demek istediğiniz anlamda bir başkasına eşit olduğunu belirlemenin genel bir yolu yoktur. Bunun istisnası, bir nesnenin kesinlikle daktilo olmadığını düşündüğünüz zamandır.

Uzun cevap

Kavram, bir nesnenin bir değer düzeyinde eşit olup olmadıklarını belirtmek için iki farklı örneğini karşılaştıran bir Eşittir yöntemidir. Ancak, bir Equalsyöntemin nasıl uygulanması gerektiğini tanımlamak belirli bir türdür . İlkel değerleri olan özniteliklerin yinelemeli bir karşılaştırması yeterli olmayabilir, nesne değerinin bir parçası olarak düşünülmeyen öznitelikler de olabilir. Örneğin,

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

Bu durumda, cMyClass'ın iki örneğinin eşit olup olmadığını ave yalnızca bönemli olup olmadığını belirlemek gerçekten önemli değildir . Bazı durumlarda cörnekler arasında değişiklik olabilir ve karşılaştırma sırasında önemli olmayabilir.

Bu sorunun, üyelerin kendileri de bir tür örnek olabileceği ve bunların her birinin eşitlik belirleme araçlarına sahip olması gerektiğinde geçerli olduğunu unutmayın.

Daha da karmaşık olan şey, JavaScript'te veri ve yöntem arasındaki ayrımın bulanık olmasıdır.

Bir nesne, olay işleyici olarak çağrılacak bir yönteme başvurabilir ve bu muhtemelen 'değer durumunun' bir parçası olarak kabul edilmez. Oysa başka bir nesneye önemli bir hesaplama yapan ve böylece bu örneği basitçe farklı bir işleve başvurduğu için diğerlerinden farklı kılan bir işlev atanmış olabilir.

Mevcut prototip yöntemlerinden birini başka bir işlev tarafından geçersiz kılan bir nesneye ne dersiniz? Yine de aynı olduğu başka bir örneğe eşit kabul edilebilir mi? Bu soru sadece her bir tip için özel durumlarda cevaplanabilir.

Daha önce belirtildiği gibi, istisna kesinlikle tipik bir nesne olacaktır. Bu durumda tek mantıklı seçim, her bir üyenin yinelemeli ve yinelemeli karşılaştırmasıdır. O zaman bile, bir fonksiyonun 'değeri' ne olduğunu sormak zorundadır?


176
_.isEqual(obj1, obj2);
Alt

12
@Harsh, cevap herhangi bir çözüm sağlayamadı çünkü hiçbiri yoktu. Java'da bile, nesne eşitliği karşılaştırmasına gümüş bir kurşun yoktur ve .equalsyöntemi doğru bir şekilde uygulamak önemsiz değildir, bu yüzden Etkili Java'ya adanmış böyle bir konu vardır .
LCN

3
@Kumar Harsh, İki nesneyi eşit yapan çok uygulamaya özgüdür; bir nesnenin her bir özelliği mutlaka dikkate alınmamalıdır, bu nedenle bir nesnenin her özelliğini kaba olarak zorlamak da somut bir çözüm değildir.
sethro

googled javascript equality object, tl aldım; dr cevap, @chovy yorumundan bir astar aldı. teşekkür ederim
Andrea

Açısal kullanıyorsanız, varangular.equals
boatcoder

507

Neden tekerleği yeniden icat ettiniz? Ver Lodash bir deneyin. İsEqual () gibi bir dizi olması gereken işlevlere sahiptir .

_.isEqual(object, other);

ECMAScript 5'i ve tarayıcıda mevcutlarsa yerel optimizasyonları kullanarak her bir anahtar değerini (bu sayfadaki diğer örnekler gibi) kaba kuvvetle kontrol edecektir .

Not: Daha önce bu cevap Underscore.js'yi önerdi , ancak lodash hataları düzeltmek ve sorunları tutarlı bir şekilde çözmek için daha iyi bir iş çıkardı.


27
Undercore's isEqual işlevi çok güzel (ama kullanmak için kendi kitaplığında çekmek zorunda - yaklaşık 3K gzip).
mckoss

29
Eğer başka alt çizgi size ne verir bakmak, çekerek pişman olmayacaksınız
PandaWood

6
Bağımlılık olarak alt çizgiye sahip olamazsanız bile, isEqual işlevini çekin, lisans gereksinimlerini karşılayın ve devam edin. Stackoverflow'da bahsedilen en kapsamlı eşitlik testidir.
Dale Anderson

7
LoDash adlı bir Undercore çatalı var ve bu yazar böyle tutarlılık sorunları ile çok ilgileniyor. LoDash ile test edin ve ne elde ettiğinizi görün.
CoolAJ86

6
mckoss tüm kütüphaneyi istemiyorsanız bağımsız modülü kullanabilirsiniz npmjs.com/package/lodash.isequal
Rob Fox

161

JavaScript for Objects içindeki varsayılan eşitlik operatörü, bellekte aynı konuma başvurduklarında true değerini verir.

var x = {};
var y = {};
var z = x;

x === y; // => false
x === z; // => true

Farklı bir eşitlik operatörüne ihtiyacınız varsa equals(other), sınıflarınıza bir yöntem veya buna benzer bir şey eklemeniz gerekir ve sorun alanınızın özellikleri tam olarak bunun ne anlama geldiğini belirler.

İşte bir oyun kartı örneği:

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

Nesneler bir JSON dizesine dönüştürülebilirse, equals () işlevini basitleştirir.
scotts

3
@scotts Her zaman değil. Nesneleri JSON'a dönüştürme ve dizeleri karşılaştırma, sıkı döngülerdeki karmaşık nesneler için hesaplama açısından yoğun olabilir. Basit nesneler için muhtemelen çok önemli değil, ama gerçekte gerçekten sizin durumunuza bağlıdır. Doğru bir çözüm, nesne kimliklerini karşılaştırmak veya her bir özelliği kontrol etmek kadar basit olabilir, ancak doğruluğu tamamen sorun alanı tarafından belirlenir.
Daniel X Moore

Veri türünü de karşılaştırmamalı mıyız ?! dönüş diğer.rank === this.rank && other.suit === this.suit;
devsathish

1
@devsathish muhtemelen değil. JavaScript türlerinde oldukça hızlı ve gevşek, ancak etki alanı türlerinizde önemliyse, türleri de kontrol etmek isteyebilirsiniz.
Daniel X Moore

7
@scotts JSON'a dönüştürmeyle ilgili diğer bir sorun, dizedeki özelliklerin sıralamasının önemli hale gelmesidir. {x:1, y:2}! =={y:2, x:1}
Stijn de Witt

81

AngularJS'de çalışıyorsanız , angular.equalsişlev iki nesnenin eşit olup olmadığını belirler. In ember.js kullanım isEqual.

  • angular.equals- Bu yöntem hakkında daha fazla bilgi için dokümanlara veya kaynağa bakın . Diziler üzerinde de derin bir karşılaştırma yapar.
  • Ember.js isEqual- Bu yöntem hakkında daha fazla bilgi için dokümanlara veya kaynağa bakın . Diziler üzerinde derin bir karşılaştırma yapmaz.

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

Bu benim versiyonum. ES5'te tanıtılan yeni Object.keys özelliğini ve + , + ve + ' dan fikirler / testler kullanıyor :

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])dönertrue
Roy Tinker

objectEquals([1,2,3],{0:1,1:2,2:3})ayrıca döner true- örneğin tip kontrolü yoktur, sadece anahtar / değer kontrolü vardır.
Roy Tinker

objectEquals(new Date(1234),1234)dönertrue
Roy Tinker

1
if (x.constructor! == y.constructor) {dönüş yanlış; } Farklı pencerelerde iki 'yeni Dize (' a ')' karşılaştırılırken bu kesilir. Değer eşitliği için, her iki nesnede String.isString öğesinin olup olmadığını kontrol etmeniz ve ardından gevşek bir eşitlik denetimi 'a == b' kullanmanız gerekir.
Triynko

1
"Değer" eşitliği ile "katı" eşitlik arasında büyük bir fark vardır ve bunlar aynı şekilde uygulanmamalıdır. Değer eşitliği, şu 4 türden biri olan temel yapı dışında türleri dikkate almamalıdır: 'nesne' (yani anahtar / değer çiftlerinin bir koleksiyonu), 'sayı', 'dize' veya 'dizi'. Bu kadar. Sayı, dize veya dizi olmayan her şey, yapıcı ne olursa olsun (çapraz pencere için güvenli) bir anahtar / değer çifti kümesi olarak karşılaştırılmalıdır. Nesneleri karşılaştırırken, değişmez sayıların değerini ve Sayı örneklerini eşitleyin, ancak dizeleri sayılara zorlamayın.
Triynko

50

Bir JSON kitaplığı kullanıyorsanız, her nesneyi JSON olarak kodlayabilir, sonra elde edilen dizeleri eşitlik için karşılaştırabilirsiniz.

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

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

NOT: Bu cevap birçok durumda işe yarayacaktır, ancak birkaç kişi yorumlarda belirtildiği gibi, çeşitli nedenlerden dolayı sorunludur. Hemen hemen tüm durumlarda daha sağlam bir çözüm bulmak isteyeceksiniz.


91
İlginç, ama bence biraz zor. Örneğin, nesne özelliklerinin her zaman aynı sırada üretileceğini% 100 garanti edebilir misiniz?
Guido

25
Bu iyi bir soru ve farklı düzenlerde aynı özelliklere sahip iki nesnenin gerçekten eşit olup olmadığı konusunda başka bir soru ortaya çıkarıyor. Ne demek istediğinize bağlı olarak, sanırım.
Joel Anair

11
Çoğu kodlayıcı ve stringizer'ın fonksiyonları görmezden geldiğini ve NaN gibi sınırsız sayıları null değerine dönüştürdüğünü unutmayın.
Stephen Belanger

4
Guido'ya katılıyorum, mülklerin sırası önemlidir ve garanti edilemez. @JoelAnair, özelliklerin değeri eşitse, farklı siparişlerde aynı özelliklere sahip iki nesne eşit kabul edilmelidir.
Juzer Ali

5
Bu olabilir alternatif bir JSON stringifier, sürekli nesne anahtarları sıralar biriyle çalışmak.
Roy Tinker

40

Kısa fonksiyonel deepEqualuygulama:

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

Düzenleme : jib'in önerisi ve ES6 ok işlevlerini kullanarak sürüm 2:

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
Basitleştirmek için reduceile değiştirebilirsiniz every.
jib

1
@nonkertompf elinden de emin: Object.keys(x).every(key => deepEqual(x[key], y[key])).
pergel

2
İki tarihi karşılaştırdığınızda bu başarısız olur
Greg

3
deepEqual ({}, []) true
değerini

3
Böyle köşe durumu için bakım eğer evet, çirkin çözüm değiştirmektir : (x === y)ile: (x === y && (x != null && y != null || x.constructor === y.constructor))
atmin

22

İki nesnenin eşit olup olmadığını test etmeye mi çalışıyorsunuz? yani: özellikleri eşit mi?

Bu durumda, muhtemelen bu durumu fark etmiş olacaksınız:

var a = { foo : "bar" };
var b = { foo : "bar" };
alert (a == b ? "Equal" : "Not equal");
// "Not equal"

böyle bir şey yapmanız gerekebilir:

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

Açıkçası bu fonksiyon biraz optimizasyon ve derin kontrol (iç içe geçmiş nesneler için) var a = { foo : { fu : "bar" } } ama fikir olsun.

FOR'un işaret ettiği gibi, bunu kendi amaçlarınız için uyarlamanız gerekebilir, örneğin: farklı sınıfların farklı "eşit" tanımları olabilir. Sadece düz nesnelerle çalışıyorsanız, yukarıdakiler yeterli olabilir, aksi takdirde özel bir MyClass.equals()işlev gitmek için bir yol olabilir.


Uzun bir yöntemdir, ancak her bir nesnenin özelliklerinin sırası hakkında herhangi bir varsayım yapmadan nesneleri tamamen test eder.
briancollins081

22

Eğer derin kopya fonksiyonu kullanışlı varsa, aşağıdaki hile kullanabilirsiniz hala kullanmak JSON.stringifyözelliklerin sırasını eşleşen ederken:

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

Demo: http://jsfiddle.net/CU3vb/3/

Gerekçe:

'Nin özellikleri obj1tek tek klonla kopyalandığından, klondaki sıraları korunacaktır. Ve öğelerinin özellikleri obj2klonuna kopyalandığında, zaten var olan özelliklerin obj1üzerine yazılacağı için, klondaki sıraları korunacaktır.


11
Tarayıcılarda / motorlarda sipariş korumasının garanti edildiğini düşünmüyorum.
Jo Liss

@JoLiss Alıntılar gerekli;) Bunu birden fazla tarayıcıda test ederek hatırlıyorum, tutarlı sonuçlar elde ediyorum. Ancak elbette, kimse gelecekteki tarayıcılarda / motorlarda aynı kalmayı garanti edemez. Bu, en iyi şekilde bir cevap (zaten cevapta da belirtildiği gibi) ve nesneleri karşılaştırmanın kesin bir yolu olduğu anlamına gelmiyordu.
Ateş Goral

1
Tabii, işte bazı işaretçiler: ECMAScript spec nesnenin "sırasız" olduğunu söylüyor ; ve mevcut tarayıcılarda gerçek sapma davranışı için bu cevap .
Jo Liss

2
@JoLiss Bunun için teşekkürler! Ancak, kod ve derlenmiş nesne arasındaki düzenin korunduğunu asla iddia etmediğimi lütfen unutmayın. Değerleri yerinde değiştirilen mülklerin sırasının korunduğunu iddia ediyordum. Çözümümün anahtarı buydu: özellik değerlerinin üzerine yazmak için bir mixin kullanmak. Uygulamaların genellikle bir çeşit hashmap kullanmayı seçtiği varsayılırsa, sadece değerlerin değiştirilmesi anahtarların sırasını korumalıdır. Aslında farklı tarayıcılarda test etmiştim.
Ateş Goral

1
@AtesGoral: Bu kısıtlamayı biraz daha açık yapmak mümkün mü (kalın, ...). Çoğu insan etrafındaki metni okumadan kopyala yapıştırma yapar ...
Willem Van Onsem

21

Node.js'de, yerelini kullanabilirsiniz require("assert").deepStrictEqual. Daha fazla bilgi: http://nodejs.org/api/assert.html

Örneğin:

var assert = require("assert");
assert.deepStrictEqual({a:1, b:2}, {a:1, b:3}); // will throw AssertionError

O döner başka örnek true/ falseyerine hataları döndürmek:

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

Chaibu özelliği de var. Bu durumda şunları kullanırsınız:var foo = { a: 1 }; var bar = { a: 1 }; expect(foo).to.deep.equal(bar); // true;
Folusho Oladipo

Node.js bazı sürümleri ayarlamak error.nameiçin "AssertionError [ERR_ASSERTION]". Bu durumda, if ifadesini ile değiştiririm if (error.code === 'ERR_ASSERTION') {.
Knute Knudsen

deepStrictEqualGidecek yol hakkında hiçbir fikrim yoktu. Neden strictEqualişe yaramadığını anlamaya çalışan beynimi sarıyordum . Fantastik.
NetOperator Wibby

19

Object, Array, String, Int gibi her şeyi karşılaştırmak için en basit ve mantıklı çözümler ...

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

Not:

  • nesnenizle val1ve val2nesnenizle değiştirmeniz gerekir
  • nesne için, her iki yan nesne için yinelemeli olarak (anahtar ile) sıralama yapmanız gerekir

6
Bunun birçok durumda işe yaramayacağını varsayıyorum çünkü nesnelerdeki anahtarların sırası önemli değil - JSON.stringifyalfabetik olarak yeniden sıralama yapmadıkça ? ( Belgelendirilmiş bulamıyorum .)
Bram Vanroy

evet haklısın ... nesne için, her iki yan nesne için özyinelemeli olarak sıralamanız gerekir
Pratik Bhalodiya

2
Dairesel referansları olan nesneler için geçerli değildir
Nate-Bit Int

13

comparableJSON karşılaştırılabilir nesnelerimin kopyalarını üretmek için bu işlevi kullanın:

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>

Testlerde kullanışlıdır (çoğu test çerçevesinin bir isişlevi vardır). Örneğin

is(JSON.stringify(comparable(x)), JSON.stringify(comparable(y)), 'x must match y');

Bir fark yakalanırsa, dizeler günlüğe kaydedilir ve farkları fark edilebilir hale getirir:

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
iyi fikir (benim durumumda sadece bir anahtar / değer çiftleri karşılaştırılacak nesneler, özel bir şey yok)
mech

10

Heres, fonksiyonel tarzda bir yaklaşım kullanarak ES6 / ES2015'te bir çözümdür:

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

demo burada bulunabilir


Nesne anahtarlarının sırası değiştiğinde çalışmaz.
Isaac Pak

9

Kimsenin buna benzer bir şey yayınlayıp yayınlamadığını bilmiyorum, ama burada nesne eşitliklerini kontrol etmek için yaptığım bir işlev var.

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

Ayrıca, özyinelemeli, bu yüzden derin eşitliği kontrol edebilir, eğer böyle adlandırırsanız.


küçük düzeltme: a ve b'deki her bir sahne geçmeden önce (Object.getOwnPropertyNames (a) .length! == Object.getOwnPropertyNames (b) .length) yanlış döndürürse bu denetimi ekleyin
18:22

1
eşitlik denetleyicisinin özyinelemeli olması gerektiği açıktır. Bu tür yinelemeli cevaplardan birinin doğru cevap olması gerektiğini düşünüyorum. Kabul edilen cevap kod vermez ve yardımcı olmaz
canbax

9

NodeJS kullananlarınız isDeepStrictEqualiçin, yerel Util kütüphanesinde adı verilen ve bunu başarabilen kullanışlı bir yöntem vardır .

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


performansının iyi olduğunu varsayalım. Telaşa gerek yok. Hatta bunu karmaşık senaryolarda da kullanıyorum. Bunu kullandığımız gibi, nesnenin özelliği Nesne veya dizi taşıyorsa endişelenmemize gerek yoktur. Json.Stringify yine de dize yapar ve javascript dizeleri karşılaştırma büyük bir anlaşma değil
TrickOrTreat

6

ES6: Yapabileceğim minimum kod bu. Tüm nesneleri dize ederek özyinelemeli olarak derin karşılaştırma yapar, tek sınırlama hiçbir yöntem veya sembolün karşılaştırılmasıdır.

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


Bu tamamen işlevsel bir cevap, teşekkürler @Adriano Spadoni. Değiştirilmiş anahtarı / niteliği nasıl alabilirim? Thanks,
digitai

1
hi @digital, hangi tuşların farklı olduğuna ihtiyacınız varsa, bu ideal işlev değildir. Diğer cevabı kontrol edin ve nesneler arasında bir döngü olan bir cevap kullanın.
Adriano Spadoni

5

_.isEqual(obj1, obj2)underscore.js kütüphanesinden kullanabilirsiniz .

İşte bir örnek:

var stooge = {name: 'moe', luckyNumbers: [13, 27, 34]};
var clone  = {name: 'moe', luckyNumbers: [13, 27, 34]};
stooge == clone;
=> false
_.isEqual(stooge, clone);
=> true

Buradan resmi belgelere bakın: http://underscorejs.org/#isEqual


5

Nesnedeki özelliklerin sırasının değişmediği varsayılarak.

JSON.stringify () , performans yönlerinden pek emin olmayan, her iki nesne türü için de derin ve derin olmayan işler için çalışır:

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
Bu OP'nin istediği şeyi yapmaz, çünkü yalnızca her iki nesnenin de aynı anahtarlara sahip olması durumunda eşleşir, eşleşmeyeceklerini belirtirler. Ayrıca, anahtarların aynı sırada olmasını gerektirir, bu da gerçekten makul değildir.
SpeedOfRound

1
Özellikleri farklı sırayla nedir ??? İyi bir yöntem değil
Vishal Sakaria

4

Birçok insanın anlamadığı bu soruna basit bir çözüm, JSON dizelerini (karakter başına) sıralamaktır. Bu genellikle burada belirtilen diğer çözümlerden daha hızlıdır:

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

Bu yöntemle ilgili bir başka yararlı şey de, JSON.stringify işlevlerine bir "replacer" işlevi ileterek karşılaştırmaları filtreleyebilirsiniz ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON / stringify # Örnek_of_using_replacer_parameter ). Aşağıda yalnızca "derp" adlı tüm nesne anahtarları karşılaştırılacaktır:

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
Oh, ben de unuttum, ama aynı nesne ise erken nesne eşitleme ve kefalet ilk test ile hızlandırılabilir: if (obj1 === obj2) true dönerse;
th317erd

11
areEqual({a: 'b'}, {b: 'a'})alır trueo zaman?
okm

Evet, gönderdikten sonra bu "çözüm" ün sorunları olduğunu fark ettim. Düzgün çalışması için sıralama algoritmasında biraz daha çalışması gerekir.
th317erd

4

Sadece bazı es6 özelliklerini kullanarak nesne karşılaştırması sürümüme katkıda bulunmak istedim. Bir sipariş dikkate alınmaz. Eğer if / else üçlüsünü dönüştürdükten sonra ben aşağıdaki ile geldim:

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

Gönderilenden daha genel bir nesne karşılaştırma fonksiyonuna ihtiyaç duyduğumda, aşağıdakileri pişirdim. Eleştiri takdir edildi ...

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
Sonunda bunun bir varyasyonunu kullandım. Üye sayma fikri için teşekkürler!
NateS

2
Değiştirme konusunda çok dikkatli olun Object.prototype- vakaların büyük çoğunluğunda önerilmemektedir (örneğin, döngülerde ... için eklemeler görünür). Belki düşünün Object.equals = function(aObj, bObj) {...}?
Roy Tinker

3

JSON nesnelerini karşılaştırıyorsanız https://github.com/mirek/node-rus-diff kullanabilirsiniz

npm install rus-diff

Kullanımı:

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

İki nesne farklıysa, MongoDB uyumlu bir {$rename:{...}, $unset:{...}, $set:{...}}nesne döndürülür.


3

Aynı sorunla karşılaştım ve kendi çözümümü yazmaya karar verdim. Ancak Dizileri Nesneler ile de karşılaştırmak istediğim için, bunun tersi de genel bir çözüm hazırladım. İşlevleri prototipe eklemeye karar verdim, ancak bunları kolayca bağımsız işlevlere yeniden yazabilirsiniz. İşte kod:

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

Bu Algoritma iki kısma ayrılmıştır; Eşittir işlevinin kendisi ve bir dizideki / nesnedeki bir özelliğin sayısal dizinini bulma işlevidir. Find fonksiyonu sadece indexof sadece sayıları ve dizeleri bulduğu ve nesne bulunmadığı için gereklidir.

Kişi şöyle diyebilir:

({a: 1, b: "h"}).equals({a: 1, b: "h"});

İşlev true veya false değerini döndürür, bu durumda true değerini döndürür. Als algoritması çok karmaşık nesneler arasında karşılaştırmaya izin verir:

({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"]})

Özelliklerin farklı bir sıralaması olsa bile, üst örnek true değerini döndürür. Dikkat edilmesi gereken küçük bir ayrıntı: Bu kod aynı tür iki değişkeni de denetler, bu nedenle "3" 3 ile aynı değildir.


3

Spagetti kodu cevaplarını görüyorum. Herhangi bir üçüncü taraf kütüphanesi kullanmadan, bu çok kolaydır.

Öncelikle iki nesneyi anahtar adlarına göre sıralayın.

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)

Sonra bunları karşılaştırmak için bir dize kullanın.

JSON.stringify(objectOne) === JSON.stringify(objectTwo)

Bu aynı zamanda derin kopya için işe yaramaz, sadece bir yineleme derinliğine sahiptir.
andras

Sanırım @andras iç içe nesnelerin anahtarlarını yinelemeli olarak sıralamanız gerektiği anlamına geliyor.
Davi Lima

2

(JSON çözüm önerdiği gibi) karma veya serileştirme karşı tavsiye ediyorum. İki nesnenin eşit olup olmadığını test etmeniz gerekiyorsa, o zaman ne anlama geldiğini tanımlamanız gerekir. Her iki nesnedeki tüm veri üyelerinin eşleşmesi veya bellek konumlarının eşleşmesi (her iki değişkenin de bellekteki aynı nesneye başvurması) eşleşmesi olabilir veya her nesnede yalnızca bir veri üyesinin eşleşmesi gerekebilir.

Son zamanlarda, her örnek oluşturulduğunda yapıcısı yeni bir kimlik oluşturan (1'den başlayıp 1 oranında arttıran) bir nesne geliştirdim. Bu nesnenin, bu id değerini başka bir nesnenin id değeriyle karşılaştıran ve eşleşmeleri durumunda true döndüren bir isEqual işlevi vardır.

Bu durumda "eşit" ifadesini id değerlerinin eşleştiği anlamına gelir. Her örneğin benzersiz bir kimliği olduğu göz önüne alındığında, bu, eşleşen nesnelerin aynı bellek konumunu işgal ettiği fikrini uygulamak için kullanılabilir. Her ne kadar bu gerekli değildir.


2

Tüm özellikler için aynı değerlere sahipse ve tüm iç içe geçmiş nesneler ve diziler için yinelemeli olarak iki nesneyi eşit olarak değerlendirmek yararlıdır. Aşağıdaki iki nesnenin de eşit olduğunu düşünüyorum:

var a = {p1: 1};
var b = {p1: 1, p2: undefined};

Benzer şekilde, dizilerde "eksik" elemanlar ve tanımlanmamış elemanlar bulunabilir. Bunlara da aynı şekilde davranırdım:

var c = [1, 2];
var d = [1, 2, undefined];

Bu eşitlik tanımını uygulayan bir işlev:

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

Kaynak kodu (yardımcı fonksiyonlar, generalType ve uniqueArray dahil): Birim Test ve Test Çalıştırıcısı .


2

Bu işlevle aşağıdaki varsayımları yapıyorum:

  1. Karşılaştırdığınız nesneleri kontrol edersiniz ve yalnızca ilkel değerleriniz vardır (yani iç içe nesneler, işlevler vb. Değil).
  2. Tarayıcınızın Object.keys desteği var .

Bu basit bir stratejinin bir örneği olarak ele alınmalıdır.

/**
 * 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

Bu, yukarıdakilerin tümü için bir ektir, bir yedek değildir. Ekstra özyinelemeli durumları kontrol etmenize gerek kalmadan sığ nesneleri karşılaştırmanız gerekiyorsa. İşte bir atış.

Şunlarla karşılaştırır: 1) Kendi mülklerinin sayısının eşitliği, 2) Anahtar adlarının eşitliği, 3) bCompareValues ​​== true ise, karşılık gelen özellik değerlerinin eşitliği ve türleri (üçlü eşitlik)

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

Basit anahtar / değer çiftleri nesne örnekleri için anahtarları karşılaştırmak için şunu kullanıyorum:

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

Anahtarlar karşılaştırıldıktan sonra, basit bir ek for..indöngü yeterlidir.

Karmaşıklık O (N * N), N anahtar sayısıdır.

Umarım tanımladığım nesneler 1000'den fazla mülke sahip olmaz ...


2

Bunun biraz eski olduğunu biliyorum, ama bu sorun için bulduğum bir çözüm eklemek istiyorum. Bir cisim vardı ve verilerinin ne zaman değiştiğini bilmek istedim. "Object.observe benzer bir şey" ve yaptığım:

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

Bu, burada çoğaltılabilir ve değerleri ve anahtarları karşılaştırmak için başka bir dizi kümesi oluşturabilir. Çok basit çünkü şimdi diziler ve nesneler farklı boyutlara sahipse false döndürecekler.


1
Bu her zaman dönecektir false, çünkü diziler değere göre karşılaştırılmaz, ör [1,2] != [1,2].
jib
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.