Bagaimana cara menentukan kesetaraan untuk dua objek JavaScript?

670

Operator kesetaraan yang ketat akan memberi tahu Anda jika dua jenis objek sama. Namun, apakah ada cara untuk mengetahui apakah dua objek sama, mirip dengan nilai kode hash di Jawa?

Pertanyaan Stack Overflow Apakah ada fungsi hashCode dalam JavaScript? mirip dengan pertanyaan ini, tetapi membutuhkan jawaban yang lebih akademis. Skenario di atas menunjukkan mengapa perlu memilikinya, dan saya ingin tahu apakah ada solusi yang setara .

Komunitas
sumber
3
Lihat juga pertanyaan ini stackoverflow.com/q/1068834/1671639
Praveen
37
Perhatikan bahwa, bahkan di Jawa, a.hashCode() == b.hashCode()tidak tidak berarti bahwa asama dengan b. Ini kondisi yang diperlukan, bukan yang cukup.
Heinzi
Tolong lihat qs ini: stackoverflow.com/questions/35933616/…
forgottofly
2
Jika Anda HARUS membandingkan objek dalam kode Anda, Anda mungkin salah menulis kode. Pertanyaan yang lebih baik mungkin: "Bagaimana saya bisa menulis kode ini sehingga saya tidak perlu membandingkan objek?"
th317erd
3
@ th317erd bisakah Anda jelaskan diri Anda sendiri? ...
El Mac

Jawaban:

175

Jawaban singkatnya

Jawaban sederhananya adalah: Tidak, tidak ada cara generik untuk menentukan bahwa suatu objek sama dengan yang lain dalam arti yang Anda maksud. Pengecualiannya adalah ketika Anda benar-benar memikirkan objek yang tidak bertuliskan.

Jawaban panjangnya

Konsepnya adalah metode Persamaan yang membandingkan dua contoh objek yang berbeda untuk menunjukkan apakah mereka sama pada tingkat nilai. Namun, tergantung pada tipe spesifik untuk menentukan bagaimana suatu Equalsmetode harus diimplementasikan. Perbandingan atribut berulang yang memiliki nilai primitif mungkin tidak cukup, mungkin ada atribut yang tidak dianggap sebagai bagian dari nilai objek. Sebagai contoh,

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

Dalam kasus di atas, ctidak terlalu penting untuk menentukan apakah ada dua instance MyClass yang sama, hanya adan bpenting. Dalam beberapa kasus cmungkin bervariasi di antara instance dan belum signifikan selama perbandingan.

Catatan masalah ini berlaku ketika anggota mungkin juga merupakan contoh dari jenis dan masing-masing akan diminta untuk memiliki sarana untuk menentukan kesetaraan.

Hal-hal rumit selanjutnya adalah bahwa dalam JavaScript perbedaan antara data dan metode menjadi kabur.

Suatu objek dapat mereferensikan suatu metode yang disebut sebagai pengendali event, dan ini kemungkinan tidak akan dianggap sebagai bagian dari 'nilai negaranya'. Sedangkan objek lain mungkin ditugaskan fungsi yang melakukan perhitungan penting dan dengan demikian membuat instance ini berbeda dari yang lain hanya karena referensi fungsi yang berbeda.

Bagaimana dengan objek yang memiliki salah satu metode prototipe yang ada ditimpa oleh fungsi lain? Bisakah itu masih dianggap sama dengan contoh lain yang identik? Pertanyaan itu hanya dapat dijawab dalam setiap kasus khusus untuk setiap jenis.

Seperti yang dinyatakan sebelumnya, pengecualian akan menjadi objek yang sama sekali tidak bertipe. Dalam hal ini satu-satunya pilihan yang masuk akal adalah perbandingan iteratif dan rekursif dari masing-masing anggota. Bahkan kemudian kita harus bertanya apa 'nilai' suatu fungsi?

AnthonyWJones
sumber
176
Jika Anda menggunakan garis bawah, Anda bisa melakukannya_.isEqual(obj1, obj2);
chovy
12
@ Keras, jawabannya gagal memberikan solusi apa pun karena tidak ada. Bahkan di Jawa, tidak ada peluru perak untuk membandingkan kesetaraan objek dan untuk menerapkan .equalsmetode ini dengan benar tidak sepele, itulah sebabnya mengapa ada topik seperti itu yang didedikasikan di Jawa Efektif .
lcn
3
@Kumar Harsh, Yang membuat dua objek sama adalah sangat spesifik untuk aplikasi; tidak setiap properti dari suatu objek harus dipertimbangkan, sehingga memaksa setiap properti dari suatu objek juga bukan solusi konkret.
sethro
googled javascript equality object, mendapat tl; balasan dr, mengambil satu-liner dari komentar @chovy. terima kasih
Andrea
Jika menggunakan sudut, Anda harusangular.equals
boatcoder
508

Mengapa menemukan kembali roda? Berikan Lodash mencoba. Ia memiliki sejumlah fungsi yang harus dimiliki seperti isEqual () .

_.isEqual(object, other);

Ini akan memaksa memeriksa setiap nilai kunci - seperti contoh lain di halaman ini - menggunakan ECMAScript 5 dan optimisasi asli jika mereka tersedia di browser.

Catatan: Sebelumnya jawaban ini merekomendasikan Underscore.js , tetapi lodash telah melakukan pekerjaan yang lebih baik untuk memperbaiki bug dan mengatasi masalah dengan konsistensi.

CoolAJ86
sumber
27
Fungsi underscore isEqual sangat bagus (tetapi Anda harus menarik pustaka mereka untuk menggunakannya - sekitar 3K gzip).
mckoss
29
jika Anda melihat apa yang garis bawah berikan, Anda tidak akan menyesal menariknya
PandaWood
6
Bahkan jika Anda tidak mampu memiliki garis bawah sebagai ketergantungan, tarik keluar fungsi isEqual, penuhi persyaratan lisensi dan lanjutkan. Sejauh ini tes kesetaraan paling komprehensif yang disebutkan pada stackoverflow.
Dale Anderson
7
Ada garpu Underscore yang disebut LoDash dan penulis itu sangat peduli dengan masalah konsistensi seperti itu. Uji dengan LoDash dan lihat apa yang Anda dapatkan.
CoolAJ86
6
@mckoss Anda dapat menggunakan modul mandiri jika Anda tidak ingin seluruh perpustakaan npmjs.com/package/lodash.isequal
Rob Fox
161

Operator kesetaraan default dalam JavaScript untuk Objek menghasilkan true ketika mereka merujuk ke lokasi yang sama dalam memori.

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

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

Jika Anda memerlukan operator kesetaraan yang berbeda, Anda perlu menambahkan equals(other)metode, atau sesuatu seperti itu ke kelas Anda dan spesifikasi domain masalah Anda akan menentukan apa artinya itu.

Berikut ini contoh kartu bermain:

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
Daniel X Moore
sumber
Jika objek (s) dapat dikonversi ke string JSON, maka itu membuat fungsi equals () sederhana.
scotts
3
@ skott Tidak selalu. Mengkonversi objek ke JSON dan membandingkan string dapat menjadi intensif secara komputasi untuk objek kompleks dalam loop ketat. Untuk objek sederhana mungkin tidak terlalu penting, tetapi pada kenyataannya itu benar-benar tergantung pada situasi spesifik Anda. Solusi yang benar mungkin sesederhana membandingkan ID objek atau memeriksa setiap properti, tetapi kebenarannya ditentukan sepenuhnya oleh domain masalah.
Daniel X Moore
Bukankah seharusnya kita membandingkan tipe data juga ?! kembalikan other.rank === this.rank && other.suit === this.suit;
devsathish
1
@devsathish mungkin tidak. Dalam tipe JavaScript cukup cepat dan longgar, tetapi jika dalam jenis domain Anda penting maka Anda mungkin ingin memeriksa jenis juga.
Daniel X Moore
7
@scott Masalah lain dengan mengkonversi ke JSON adalah urutan properti dalam string menjadi signifikan. {x:1, y:2}! =={y:2, x:1}
Stijn de Witt
81

Jika Anda bekerja di AngularJS , angular.equalsfungsi akan menentukan apakah dua objek sama. Dalam penggunaan Ember.jsisEqual .

  • angular.equals- Lihat dokumen atau sumber untuk lebih lanjut tentang metode ini. Itu juga membandingkan dalam pada array.
  • Ember.js isEqual- Lihat dokumen atau sumber untuk lebih lanjut tentang metode ini. Itu tidak melakukan perbandingan yang mendalam pada array.

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>

Troy Harvey
sumber
66

Ini versi saya. Itu menggunakan fitur Object.keys baru yang diperkenalkan di ES5 dan ide / tes dari + , + dan + :

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

Ebrahim Byagowi
sumber
objectEquals([1,2,undefined],[1,2])kembalitrue
Roy Tinker
objectEquals([1,2,3],{0:1,1:2,2:3})juga mengembalikan true- misalnya tidak ada pemeriksaan tipe, hanya pemeriksaan kunci / nilai.
Roy Tinker
objectEquals(new Date(1234),1234)kembalitrue
Roy Tinker
1
if (x.constructor! == y.constructor) {return false; } Ini akan pecah ketika membandingkan dua 'String baru (' a ')' di jendela yang berbeda. Untuk kesetaraan nilai, Anda harus memeriksa apakah String.isString pada kedua objek, lalu gunakan cek kesetaraan longgar 'a == b'.
Triynko
1
Ada perbedaan besar antara kesetaraan "nilai" dan kesetaraan "ketat" dan mereka seharusnya tidak dilaksanakan dengan cara yang sama. Kesetaraan nilai seharusnya tidak memedulikan jenis, selain dari struktur dasar, yang merupakan salah satu dari 4 ini: 'objek' (yaitu kumpulan pasangan kunci / nilai), 'angka', 'string', atau 'array'. Itu dia. Apa pun yang bukan angka, string, atau array, harus dibandingkan sebagai satu set pasangan kunci / nilai, terlepas dari apa konstruktornya (lintas-jendela-aman). Saat membandingkan objek, samakan nilai angka literal dan instance Angka, tetapi jangan paksakan string ke angka.
Triynko
50

Jika Anda menggunakan pustaka JSON, Anda bisa menyandikan setiap objek sebagai JSON, lalu membandingkan string yang dihasilkan untuk kesetaraan.

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

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

CATATAN: Meskipun jawaban ini akan berfungsi dalam banyak kasus, karena beberapa orang telah menunjukkan dalam komentar itu bermasalah karena berbagai alasan. Dalam hampir semua kasus, Anda ingin mencari solusi yang lebih kuat.

Joel Anair
sumber
91
Menarik, tapi agak rumit menurut saya. Misalnya, dapatkah Anda 100% menjamin bahwa properti objek akan dihasilkan selalu dalam urutan yang sama?
Guido
25
Itu pertanyaan yang bagus, dan menimbulkan pertanyaan lain, apakah dua objek dengan properti yang sama dalam urutan berbeda benar-benar sama atau tidak. Tergantung pada apa yang Anda maksud dengan yang sama, saya kira.
Joel Anair
11
Perhatikan bahwa sebagian besar penyandi dan pengali mengabaikan fungsi dan mengonversi angka yang tidak terbatas, seperti NaN, menjadi nol.
Stephen Belanger
4
Saya setuju dengan Guido, urutan properti itu penting dan tidak dapat dijamin. @ JoelAnair, saya pikir dua objek dengan properti yang sama dalam urutan berbeda harus dianggap sama jika nilai propertinya sama.
Juzer Ali
5
Ini dapat bekerja dengan stringifier JSON alternatif, yang mengurutkan kunci objek secara konsisten.
Roy Tinker
40

deepEqualImplementasi fungsional pendek :

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

Sunting : versi 2, menggunakan saran jib dan fungsi panah 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);
}
atmin
sumber
5
Anda dapat mengganti reducedengan everyuntuk menyederhanakan.
jib
1
@nonkertompf yakin dia bisa: Object.keys(x).every(key => deepEqual(x[key], y[key])).
jib
2
Ini gagal ketika Anda membandingkan dua tanggal
Greg
3
deepEqual ({}, []) mengembalikan true
AlexMorley-Finch
3
ya, jika Anda peduli untuk sudut seperti itu, solusi jelek adalah mengganti : (x === y)dengan: (x === y && (x != null && y != null || x.constructor === y.constructor))
atmin
22

Apakah Anda mencoba menguji apakah dua objek itu sama? yaitu: propertinya sama?

Jika ini masalahnya, Anda mungkin telah memperhatikan situasi ini:

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

Anda mungkin harus melakukan sesuatu seperti ini:

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

Jelas fungsi itu bisa dilakukan dengan sedikit optimasi, dan kemampuan untuk melakukan pemeriksaan mendalam (untuk menangani objek bersarang: var a = { foo : { fu : "bar" } } tetapi Anda mendapatkan idenya.

Seperti yang ditunjukkan FOR, Anda mungkin harus menyesuaikan ini untuk tujuan Anda sendiri, misalnya: kelas yang berbeda mungkin memiliki definisi "sama" yang berbeda. Jika Anda hanya bekerja dengan objek biasa, hal di atas mungkin sudah cukup, jika tidak, MyClass.equals()fungsi kustom mungkin merupakan cara untuk melakukannya.

nickf
sumber
Ini adalah metode yang panjang tetapi benar-benar menguji objek tanpa membuat asumsi pada urutan properti di setiap objek.
briancollins081
22

Jika Anda memiliki fungsi penyalinan dalam, Anda dapat menggunakan trik berikut untuk tetap menggunakan JSON.stringifysembari mencocokkan urutan properti:

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/

Alasan:

Karena properti dari obj1disalin ke klon satu per satu, pesanan mereka di klon akan dipertahankan. Dan ketika properti obj2disalin ke klon, karena properti yang sudah ada di obj1hanya akan ditimpa, pesanan mereka di klon akan dipertahankan.

Makan Goral
sumber
11
Saya tidak berpikir pelestarian pesanan dijamin di seluruh browser / mesin.
Jo Liss
@JoLiss Kutipan diperlukan;) Saya ingat menguji ini di beberapa browser, mendapatkan hasil yang konsisten. Tapi tentu saja, tidak ada yang bisa menjamin perilaku tetap sama di browser / mesin masa depan. Ini adalah trik (seperti yang sudah disebutkan dalam jawaban) di terbaik, dan saya tidak bermaksud itu cara yang pasti untuk membandingkan objek.
Ates Goral
1
Tentu, inilah beberapa petunjuk: ECMAScript spec mengatakan objek "tidak berurutan" ; dan jawaban ini untuk perilaku menyimpang yang sebenarnya pada browser saat ini.
Jo Liss
2
@ JoLiss Terima kasih untuk itu! Tapi tolong dicatat saya tidak pernah mengklaim pelestarian urutan antara kode dan objek yang dikompilasi. Saya mengklaim pelestarian urutan properti yang nilainya diganti di tempat. Itulah kunci dari solusi saya: menggunakan mixin untuk hanya menimpa nilai properti. Dengan asumsi implementasi umumnya memilih untuk menggunakan semacam hashmap, mengganti nilai yang adil harus menjaga urutan kunci. Sebenarnya inilah yang saya uji di browser yang berbeda.
Ates Goral
1
@AtesGoral: apakah mungkin membuat batasan ini lebih eksplisit (cetak tebal, ...). Kebanyakan orang hanya melakukan copy-paste tanpa membaca teks di sekitarnya ...
Willem Van Onsem
21

Di Node.js, Anda bisa menggunakan aslinya require("assert").deepStrictEqual. Info lebih lanjut: http://nodejs.org/api/assert.html

Sebagai contoh:

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

Contoh lain yang mengembalikan true/ falsebukannya mengembalikan kesalahan:

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;
};
Rafael Xavier
sumber
Chaimemiliki fitur ini juga. Dalam hal ini, Anda akan menggunakan:var foo = { a: 1 }; var bar = { a: 1 }; expect(foo).to.deep.equal(bar); // true;
Folusho Oladipo
Beberapa versi Node.js diatur error.nameke "AssertionError [ERR_ASSERTION]". Dalam hal ini, saya akan mengganti pernyataan if dengan if (error.code === 'ERR_ASSERTION') {.
Knute Knudsen
Saya tidak tahu deepStrictEqualadalah cara untuk pergi. Aku telah mengacaukan otakku mencoba mencari tahu mengapa strictEqualtidak bekerja. Fantastis.
NetOperator Wibby
19

Solusi paling sederhana dan logis untuk membandingkan semuanya Seperti Object, Array, String, Int ...

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

catatan:

  • Anda perlu mengganti val1dan val2dengan Obyek Anda
  • untuk objek, Anda harus mengurutkan (dengan kunci) secara rekursif untuk kedua objek samping
Pratik Bhalodiya
sumber
6
Saya mengasumsikan bahwa ini tidak akan berhasil dalam banyak kasus karena urutan kunci pada objek tidak masalah - kecuali JSON.stringifyapakah pemesanan ulang alfabet? (Yang saya tidak dapat menemukan didokumentasikan .)
Bram Vanroy
yup Anda benar ... untuk objek, Anda harus menyortir secara rekursif untuk kedua objek samping
Pratik Bhalodiya
2
Ini tidak berfungsi untuk objek dengan referensi melingkar
Nate-Bit Int
13

Saya menggunakan comparablefungsi ini untuk menghasilkan salinan objek saya yang sebanding dengan 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>

Sangat berguna dalam tes (sebagian besar kerangka uji memiliki isfungsi). Misalnya

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

Jika ada perbedaan, string dicatat, membuat perbedaan terlihat:

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}}.
jib
sumber
1
ide bagus (dalam kasus saya objek yang akan dibandingkan hanya pasangan kunci / nilai, tidak ada hal-hal khusus)
mech
10

Inilah solusi di ES6 / ES2015 menggunakan pendekatan gaya fungsional:

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 tersedia di sini

Alan R. Soares
sumber
Tidak berfungsi jika urutan kunci objek telah berubah.
Isaac Pak
9

Saya tidak tahu apakah ada orang yang memposting sesuatu yang mirip dengan ini, tapi inilah fungsi yang saya buat untuk memeriksa persamaan objek.

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

Selain itu, bersifat rekursif, sehingga juga dapat memeriksa kesetaraan yang mendalam, jika Anda menyebutnya demikian.

Zac
sumber
koreksi kecil: sebelum melalui setiap alat peraga di a dan b tambahkan cek ini jika (Object.getOwnPropertyNames (a) .length! == Object.getOwnPropertyNames (b) .length) return false
Hith
1
jelas bahwa pemeriksa kesetaraan yang tepat harus bersifat rekursif. Saya pikir salah satu jawaban rekursif seperti itu harus menjadi jawaban yang benar. Jawaban yang diterima tidak memberikan kode dan tidak membantu
canbax
9

Bagi Anda yang menggunakan NodeJS, ada metode yang mudah dipanggil isDeepStrictEqualpada pustaka Util asli yang dapat mencapai ini.

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

Vaelin
sumber
kinerjanya dianggap baik. Jangan khawatir. Bahkan saya menggunakan ini dalam skenario yang kompleks juga. Seperti ketika kita menggunakan ini, kita tidak perlu khawatir jika properti objek menggunakan Object atau array. Json.Stringify membuatnya tetap string dan perbandingan string dalam javascript bukan masalah besar
TrickOrTreat
6

ES6: Kode minimum yang bisa saya selesaikan, adalah ini. Ini melakukan perbandingan mendalam secara rekursif dengan merangkai semua objek, satu-satunya batasan adalah tidak ada metode atau simbol yang dibandingkan.

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
sumber
Ini adalah jawaban yang berfungsi penuh, terima kasih @Adriano Spadoni. Apakah Anda tahu bagaimana saya bisa mendapatkan kunci / atribut yang dimodifikasi? Terima kasih,
digitai
1
hai @ digital, jika Anda memerlukan tombol yang berbeda, ini bukan fungsi yang ideal. Periksa jawaban yang lain dan gunakan satu dengan loop melalui objek.
Adriano Spadoni
5

Anda dapat menggunakan _.isEqual(obj1, obj2)dari perpustakaan underscore.js.

Berikut ini sebuah contoh:

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

Lihat dokumentasi resmi dari sini: http://underscorejs.org/#isEqual

Bentaiba Miled Basma
sumber
5

Dengan asumsi bahwa urutan properti di objek tidak berubah.

JSON.stringify () bekerja untuk kedua jenis objek yang dalam dan tidak dalam, tidak terlalu yakin pada aspek kinerja:

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

Mohammed Zameer
sumber
1
Ini tidak melakukan apa yang diinginkan OP, karena hanya akan cocok jika kedua objek memiliki semua kunci yang sama, yang mereka nyatakan tidak. Itu juga akan membutuhkan kunci dalam urutan yang sama, yang juga tidak masuk akal.
SpeedOfRound
1
Apa sifat-sifatnya dalam urutan yang berbeda ??? Bukan metode yang bagus
Vishal Sakaria
4

Solusi sederhana untuk masalah ini yang tidak disadari banyak orang adalah dengan mengurutkan string JSON (per karakter). Ini juga biasanya lebih cepat daripada solusi lain yang disebutkan di sini:

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

Hal lain yang bermanfaat tentang metode ini adalah Anda dapat memfilter perbandingan dengan mengirimkan fungsi "replacer" ke fungsi JSON.stringify ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON / stringify # Example_of_using_replacer_parameter ). Berikut ini hanya akan membandingkan semua kunci objek yang diberi nama "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;
});
th317erd
sumber
1
Oh, saya juga lupa, tetapi fungsinya dapat dipercepat dengan terlebih dahulu menguji objek yang menyamakan dan bailing awal jika mereka adalah objek yang sama: if (obj1 === obj2) return true;
th317erd
11
areEqual({a: 'b'}, {b: 'a'})mengerti true?
okm
Ya, saya menyadari setelah memposting bahwa "solusi" ini memiliki masalah. Perlu sedikit lebih banyak pekerjaan dalam algoritma pengurutan untuk benar-benar berfungsi dengan baik.
th317erd
4

Hanya ingin berkontribusi versi perbandingan objek saya menggunakan beberapa fitur es6. Itu tidak memperhitungkan pesanan. Setelah mengubah semua jika / selain menjadi ternary, saya datang dengan mengikuti:

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;

        }
    )
}
Egor Litvinchuk
sumber
3

Membutuhkan fungsi perbandingan objek yang lebih umum daripada yang telah diposting, saya memasak yang berikut ini. Kritik dihargai ...

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;
}
Liam
sumber
2
Saya akhirnya menggunakan variasi ini. Terima kasih atas ide menghitung anggota!
NateS
2
Berhati-hatilah dalam memodifikasi Object.prototype- dalam sebagian besar kasus tidak disarankan (tambahan muncul di semua untuk..di loop, misalnya). Mungkin mempertimbangkan Object.equals = function(aObj, bObj) {...}?
Roy Tinker
3

Jika Anda membandingkan objek JSON, Anda dapat menggunakan https://github.com/mirek/node-rus-diff

npm install rus-diff

Pemakaian:

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

Jika dua objek berbeda, {$rename:{...}, $unset:{...}, $set:{...}}objek seperti MongoDB yang kompatibel dikembalikan.

Mirek Rusin
sumber
3

Saya menghadapi masalah yang sama dan memutuskan untuk menulis solusi sendiri. Tetapi karena saya ingin juga membandingkan Array dengan Objects dan sebaliknya, saya membuat solusi generik. Saya memutuskan untuk menambahkan fungsi ke prototipe, tetapi orang dapat dengan mudah menulis ulang mereka ke fungsi mandiri. Ini kodenya:

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

Algoritma ini dibagi menjadi dua bagian; Fungsi equals itu sendiri dan fungsi untuk menemukan indeks numerik properti dalam array / objek. Fungsi find hanya diperlukan karena indexof hanya menemukan angka dan string dan tidak ada objek.

Orang dapat menyebutnya seperti ini:

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

Fungsi mengembalikan benar atau salah, dalam hal ini benar. Algoritma juga memungkinkan perbandingan antara objek yang sangat kompleks:

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

Contoh atas akan mengembalikan true, bahkan properti memiliki urutan yang berbeda. Satu detail kecil yang harus diperhatikan: Kode ini juga memeriksa jenis dua variabel yang sama, jadi "3" tidak sama dengan 3.

Sir_baaron
sumber
3

Saya melihat jawaban kode spaghetti. Tanpa menggunakan lib pihak ketiga, ini sangat mudah.

Pertama-tama, urutkan kedua objek dengan memasukkan nama kunci mereka.

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)

Kemudian cukup gunakan string untuk membandingkannya.

JSON.stringify(objectOne) === JSON.stringify(objectTwo)
Oliver Dixon
sumber
Ini juga tidak berfungsi untuk penyalinan dalam, hanya memiliki satu kedalaman iterasi.
andras
Saya pikir @andras berarti Anda perlu mengurutkan kunci objek bersarang secara rekursif.
Davi Lima
2

Saya akan menyarankan agar hashing atau serialisasi (seperti yang disarankan solusi JSON). Jika Anda perlu menguji apakah dua objek sama, maka Anda perlu mendefinisikan apa yang sama artinya. Bisa jadi semua anggota data di kedua objek cocok, atau bisa jadi lokasi memori harus cocok (artinya kedua variabel mereferensikan objek yang sama di memori), atau mungkin hanya satu anggota data di setiap objek yang harus cocok.

Baru-baru ini saya mengembangkan objek yang konstruktornya membuat id baru (mulai dari 1 dan bertambah 1) setiap kali sebuah instance dibuat. Objek ini memiliki fungsi isEqual yang membandingkan nilai id dengan nilai id dari objek lain dan mengembalikan true jika cocok.

Dalam hal ini saya mendefinisikan "sama" sebagai makna nilai-nilai id cocok. Mengingat bahwa setiap instance memiliki id unik ini dapat digunakan untuk menegakkan gagasan bahwa objek yang cocok juga menempati lokasi memori yang sama. Meskipun itu tidak perlu.

Bernard Igiri
sumber
2

Ini berguna untuk mempertimbangkan dua objek yang sama jika mereka memiliki semua nilai yang sama untuk semua properti dan secara rekursif untuk semua objek dan array bersarang. Saya juga menganggap dua objek berikut ini sama:

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

Demikian pula, array dapat memiliki elemen "hilang" dan elemen tidak terdefinisi. Saya akan memperlakukan mereka yang sama juga:

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

Fungsi yang mengimplementasikan definisi persamaan ini:

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

Kode sumber (termasuk fungsi helper, generalType, dan uniqueArray): Unit Test dan Test Runner di sini .

mckoss
sumber
2

Saya membuat asumsi berikut dengan fungsi ini:

  1. Anda mengontrol objek yang Anda bandingkan dan Anda hanya memiliki nilai primitif (mis. Bukan objek, fungsi, dll.).
  2. Browser Anda memiliki dukungan untuk Object.keys .

Ini harus diperlakukan sebagai peragaan strategi sederhana.

/**
 * 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;
}
Aldo Fregoso
sumber
2

Ini merupakan tambahan untuk semua hal di atas, bukan pengganti. Jika Anda perlu mempercepat objek membandingkan-dangkal tanpa perlu memeriksa kasus rekursif tambahan. Ini sebuah tembakan.

Ini membandingkan untuk: 1) Kesetaraan jumlah properti sendiri, 2) Kesetaraan nama kunci, 3) jika bCompareValues ​​== true, Kesetaraan nilai properti yang sesuai dan tipenya (triple equality)

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;
}
Lex
sumber
2

Untuk membandingkan kunci untuk instance objek kunci / nilai sederhana, saya menggunakan:

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

Setelah kunci dibandingkan, for..inloop tambahan sederhana sudah cukup.

Kompleksitas adalah O (N * N) dengan N adalah jumlah kunci.

Saya harap / tebak objek yang saya tentukan tidak akan menampung lebih dari 1000 properti ...

OLAHRAGA HEFEust
sumber
2

Saya tahu ini agak lama, tetapi saya ingin menambahkan solusi yang saya temukan untuk masalah ini. Saya memiliki objek dan saya ingin tahu kapan datanya berubah. "sesuatu yang mirip dengan Object.observe" dan apa yang saya lakukan adalah:

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

Ini di sini dapat diduplikasi dan membuat set array lainnya untuk membandingkan nilai dan kunci. Ini sangat sederhana karena sekarang array dan akan mengembalikan false jika objek memiliki ukuran berbeda.

inoabrian
sumber
1
Ini akan selalu kembali false, karena array tidak membandingkan dengan nilai, mis [1,2] != [1,2].
jib