Làm cách nào để xác định đẳng thức cho hai đối tượng JavaScript?


668

Một toán tử đẳng thức nghiêm ngặt sẽ cho bạn biết nếu hai loại đối tượng bằng nhau. Tuy nhiên, có cách nào để biết hai đối tượng có bằng nhau không, giống như giá trị mã băm trong Java?

Câu hỏi về Stack Overflow Có bất kỳ loại hàm hashCode nào trong JavaScript không? tương tự như câu hỏi này, nhưng đòi hỏi một câu trả lời mang tính học thuật hơn. Kịch bản trên cho thấy lý do tại sao cần phải có một cái, và tôi tự hỏi liệu có giải pháp tương đương nào không .


3
Đồng thời xem xét câu hỏi này stackoverflow.com/q/1068834/1671639
Praveen

37
Lưu ý rằng, ngay cả trong Java, a.hashCode() == b.hashCode()không không có nghĩa là atương đương với b. Đó là một điều kiện cần thiết, không phải là một điều kiện đủ.
Heinzi


2
Nếu bạn phải so sánh các đối tượng trong mã của mình thì có lẽ bạn đang viết mã sai. Câu hỏi tốt hơn có thể là: "Làm thế nào tôi có thể viết mã này để tôi không phải so sánh các đối tượng?"
th317erd

3
@ th317erd bạn có thể vui lòng giải thích cho mình không? ...
El Mac

Câu trả lời:


175

Câu trả lời ngắn

Câu trả lời đơn giản là: Không, không có phương tiện chung nào để xác định rằng một đối tượng bằng với đối tượng khác theo nghĩa của bạn. Ngoại lệ là khi bạn đang suy nghĩ nghiêm túc về một đối tượng là không chữ.

Câu trả lời dài

Khái niệm này là một phương thức Equals so sánh hai trường hợp khác nhau của một đối tượng để cho biết liệu chúng có bằng nhau ở mức giá trị hay không. Tuy nhiên, tùy thuộc vào loại cụ thể để xác định cách Equalsthức thực hiện một phương thức. Một so sánh lặp lại của các thuộc tính có giá trị nguyên thủy có thể không đủ, cũng có thể có các thuộc tính không được coi là một phần của giá trị đối tượng. Ví dụ,

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

Trong trường hợp trên, ckhông thực sự quan trọng để xác định xem hai trường hợp MyClass có bằng nhau hay không, chỉ abquan trọng. Trong một số trường hợp ccó thể khác nhau giữa các trường hợp và không đáng kể trong quá trình so sánh.

Lưu ý vấn đề này được áp dụng khi chính các thành viên cũng có thể là một thể loại và mỗi người đều được yêu cầu phải có một phương tiện để xác định sự bình đẳng.

Điều phức tạp hơn nữa là trong JavaScript, sự khác biệt giữa dữ liệu và phương thức bị mờ đi.

Một đối tượng có thể tham chiếu một phương thức được gọi là xử lý sự kiện và điều này có thể sẽ không được coi là một phần của 'trạng thái giá trị' của nó. Trong khi đó một đối tượng khác có thể được chỉ định một hàm thực hiện một phép tính quan trọng và do đó làm cho trường hợp này khác với các đối tượng khác đơn giản chỉ vì nó tham chiếu một hàm khác.

Điều gì về một đối tượng có một trong các phương thức nguyên mẫu hiện có của nó bị ghi đè bởi một chức năng khác? Nó vẫn có thể được coi là bằng với một trường hợp khác mà nó giống hệt nhau? Câu hỏi đó chỉ có thể được trả lời trong từng trường hợp cụ thể cho từng loại.

Như đã nêu trước đó, ngoại lệ sẽ là một đối tượng không lỗi chính tả. Trong trường hợp đó, sự lựa chọn hợp lý duy nhất là so sánh lặp lại và đệ quy của từng thành viên. Thậm chí sau đó người ta phải hỏi 'giá trị' của hàm là gì?


174
Nếu bạn đang sử dụng dấu gạch dưới, bạn chỉ có thể thực hiện_.isEqual(obj1, obj2);
chovy

12
@Harsh, câu trả lời không đưa ra giải pháp nào vì không có. Ngay cả trong Java, không có viên đạn bạc nào để so sánh bình đẳng đối tượng và để thực hiện chính xác .equalsphương thức này không phải là chuyện nhỏ, đó là lý do tại sao có một chủ đề như vậy dành riêng cho Java hiệu quả .
lcn

3
@Kumar Harsh, Điều gì làm cho hai đối tượng bằng nhau là ứng dụng rất cụ thể; không phải mọi tài sản của một đối tượng nhất thiết phải được xem xét, do đó, vũ phu buộc mọi tài sản của một đối tượng cũng không phải là một giải pháp cụ thể.
sethro

googled javascript equality object, đã nhận được tl; dr trả lời, lấy một-liner từ bình luận @chovy. cảm ơn bạn
Andrea

Nếu sử dụng góc cạnh, bạn cóangular.equals
lái thuyền

506

Tại sao phải phát minh lại bánh xe? Cho Lodash một thử. Nó có một số hàm phải có như isEqual () .

_.isEqual(object, other);

Nó sẽ mạnh mẽ kiểm tra từng giá trị khóa - giống như các ví dụ khác trên trang này - sử dụng ECMAScript 5 và tối ưu hóa gốc nếu chúng có sẵn trong trình duyệt.

Lưu ý: Trước đây, câu trả lời này đã đề xuất Underscore.js , nhưng lodash đã thực hiện tốt hơn việc sửa lỗi và giải quyết các vấn đề một cách nhất quán.


27
Hàm isEqual của Underscore rất đẹp (nhưng bạn phải kéo vào thư viện của họ để sử dụng nó - khoảng 3K được nén).
mckoss

29
nếu bạn nhìn vào những gì khác gạch dưới mang lại cho bạn, bạn sẽ không hối tiếc khi kéo nó vào
PandaWood

6
Ngay cả khi bạn không đủ khả năng để có dấu gạch dưới làm phụ thuộc, hãy kéo hàm isEqual ra, đáp ứng các yêu cầu cấp phép và tiếp tục. Đó là thử nghiệm bình đẳng toàn diện nhất được đề cập trên stackoverflow.
Dale Anderson

7
Có một nhánh của Underscore được gọi là LoDash và tác giả đó rất quan tâm đến các vấn đề nhất quán như vậy. Kiểm tra với LoDash và xem những gì bạn nhận được.
CoolAJ86

6
@mckoss bạn có thể sử dụng mô-đun độc lập nếu bạn không muốn toàn bộ thư viện npmjs.com/package/lodash.isequal
Rob Fox

161

Toán tử đẳng thức mặc định trong JavaScript cho Đối tượng mang lại giá trị đúng khi chúng tham chiếu đến cùng một vị trí trong bộ nhớ.

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

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

Nếu bạn yêu cầu một toán tử đẳng thức khác, bạn sẽ cần thêm một equals(other)phương thức hoặc một cái gì đó giống như nó vào các lớp của bạn và các chi tiết cụ thể của miền vấn đề của bạn sẽ xác định chính xác điều đó có nghĩa là gì.

Đây là một ví dụ về thẻ chơ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

Nếu (các) đối tượng có thể được chuyển đổi thành một chuỗi JSON, thì nó làm cho hàm equals () trở nên đơn giản.
scotts

3
@scotts Không phải lúc nào. Chuyển đổi các đối tượng thành JSON và so sánh các chuỗi có thể trở nên chuyên sâu về mặt tính toán cho các đối tượng phức tạp trong các vòng lặp chặt chẽ. Đối với các đối tượng đơn giản, nó có thể không quan trọng lắm, nhưng trong thực tế, nó thực sự phụ thuộc vào tình huống cụ thể của bạn. Một giải pháp đúng có thể đơn giản như so sánh ID đối tượng hoặc kiểm tra từng thuộc tính, nhưng tính chính xác của nó được quyết định hoàn toàn bởi miền vấn đề.
Daniel X Moore

Chúng ta không nên so sánh loại dữ liệu là tốt?! return other.rank === this.rank && other.suit === this.suit;
devsathish

1
@devsathish có lẽ là không. Trong các loại JavaScript khá nhanh và lỏng lẻo, nhưng nếu trong các loại tên miền của bạn quan trọng thì bạn cũng có thể muốn kiểm tra các loại.
Daniel X Moore

7
@scotts Vấn đề khác khi chuyển đổi sang JSON là thứ tự của các thuộc tính trong chuỗi trở nên quan trọng. {x:1, y:2}! =={y:2, x:1}
Stijn de Witt

81

Nếu bạn đang làm việc trong AngularJS , angular.equalshàm sẽ xác định xem hai đối tượng có bằng nhau không. Trong Ember.js sử dụng isEqual.

  • angular.equals- Xem tài liệu hoặc nguồn để biết thêm về phương pháp này. Nó cũng làm một so sánh sâu về mảng.
  • Ember.js isEqual- Xem tài liệu hoặc nguồn để biết thêm về phương pháp này. Nó không làm một so sánh sâu về mảng.

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

Đây là phiên bản của tôi. Nó đang sử dụng tính năng Object.keys mới được giới thiệu trong ES5 và ý tưởng / thử nghiệm từ + , ++ :

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])trở vềtrue
Roy Tinker

objectEquals([1,2,3],{0:1,1:2,2:3})cũng trả về true- ví dụ: không có kiểm tra kiểu, chỉ kiểm tra khóa / giá trị.
Roy Tinker

objectEquals(new Date(1234),1234)trở vềtrue
Roy Tinker

1
if (x.constructor! == y.constructor) {return false; } Điều này sẽ bị hỏng khi so sánh hai 'Chuỗi mới (' a ')' trong các cửa sổ khác nhau. Để có giá trị bằng nhau, bạn phải kiểm tra xem String.isString trên cả hai đối tượng hay không, sau đó sử dụng kiểm tra đẳng thức lỏng lẻo 'a == b'.
Triynko

1
Có một sự khác biệt rất lớn giữa bình đẳng "giá trị" và bình đẳng "nghiêm ngặt" và chúng không nên được thực hiện theo cùng một cách. Bình đẳng giá trị không nên quan tâm đến các loại, ngoài cấu trúc cơ bản, là một trong 4: 'object' (nghĩa là một tập hợp các cặp khóa / giá trị), 'số', 'chuỗi' hoặc 'mảng'. Đó là nó. Bất cứ thứ gì không phải là số, chuỗi hoặc mảng, nên được so sánh như một tập hợp các cặp khóa / giá trị, bất kể hàm tạo là gì (an toàn chéo cửa sổ). Khi so sánh các đối tượng, hãy đánh giá giá trị của số bằng chữ và thể hiện của Số, nhưng không ép buộc chuỗi thành số.
Triynko

50

Nếu bạn đang sử dụng thư viện JSON, bạn có thể mã hóa từng đối tượng dưới dạng JSON, sau đó so sánh các chuỗi kết quả cho sự bằng nhau.

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

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

LƯU Ý: Mặc dù câu trả lời này sẽ hoạt động trong nhiều trường hợp, như nhiều người đã chỉ ra trong các ý kiến, nó có vấn đề vì nhiều lý do. Trong hầu hết các trường hợp, bạn sẽ muốn tìm một giải pháp mạnh mẽ hơn.


91
Thú vị, nhưng một chút khó khăn trong quan điểm của tôi. Ví dụ, bạn có thể đảm bảo 100% rằng các thuộc tính đối tượng sẽ được tạo luôn theo cùng một thứ tự không?
Guido

25
Đó là một câu hỏi hay, và đặt ra một câu hỏi khác, liệu hai đối tượng có cùng thuộc tính theo các thứ tự khác nhau có thực sự bằng nhau hay không. Tôi phụ thuộc vào những gì bạn có nghĩa là bằng nhau, tôi đoán.
Joel Anair

11
Lưu ý rằng hầu hết các bộ mã hóa và chuỗi ký tự bỏ qua các hàm và chuyển đổi các số không xác định, như NaN, thành null.
Stephen Belanger

4
Tôi đồng ý với Guido, thứ tự các tài sản rất quan trọng và nó không thể được đảm bảo. @JoelAnair, tôi nghĩ hai đối tượng có cùng thuộc tính theo thứ tự khác nhau nên được coi là bằng nhau nếu giá trị của các thuộc tính bằng nhau.
Juzer Ali

5
Điều này có thể hoạt động với một bộ tạo chuỗi JSON thay thế, một bộ sắp xếp các khóa đối tượng một cách nhất quán.
Roy Tinker

40

deepEqualThực hiện chức năng ngắn :

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

Chỉnh sửa : phiên bản 2, sử dụng chức năng gợi ý của jib và mũi tên 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);
}

5
Bạn có thể thay thế reducebằng everyđể đơn giản hóa.
jib

1
@nonkertompf chắc chắn anh ấy có thể : Object.keys(x).every(key => deepEqual(x[key], y[key])).
jib

2
Điều này thất bại khi bạn so sánh hai ngày
Greg

3
deepEqual ({}, []) trả về đúng
AlexMorley-Finch

3
vâng, nếu bạn quan tâm đến trường hợp góc như vậy, giải pháp xấu xí là thay thế : (x === y)bằng: (x === y && (x != null && y != null || x.constructor === y.constructor))
atmin 16/07/18

22

Bạn đang cố kiểm tra xem hai đối tượng có bằng nhau không? tức là: tính chất của chúng bằng nhau?

Nếu đây là trường hợp, có lẽ bạn sẽ nhận thấy tình huống này:

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

bạn có thể phải làm một cái gì đó như thế này:

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

Rõ ràng chức năng đó có thể thực hiện với khá nhiều tối ưu hóa và khả năng kiểm tra sâu (để xử lý các đối tượng lồng nhau var a = { foo : { fu : "bar" } }:) nhưng bạn có ý tưởng.

Như FOR đã chỉ ra, bạn có thể phải điều chỉnh điều này cho mục đích riêng của mình, ví dụ: các lớp khác nhau có thể có các định nghĩa khác nhau về "bằng nhau". Nếu bạn chỉ làm việc với các đối tượng đơn giản, ở trên có thể đủ, nếu không, một MyClass.equals()chức năng tùy chỉnh có thể là cách để đi.


Nó là một phương thức dài nhưng nó hoàn toàn kiểm tra các đối tượng mà không đưa ra bất kỳ giả định nào về thứ tự của các thuộc tính trong mỗi đối tượng.
briancollins081

22

Nếu bạn có một chức năng sao chép sâu tiện dụng, bạn có thể sử dụng mẹo sau để vẫn sử dụng JSON.stringifytrong khi khớp với thứ tự của các thuộc tính:

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

Bản trình diễn: http://jsfiddle.net/CU3vb/3/

Cơ sở lý luận:

Vì các thuộc tính của obj1được sao chép từng bản một, nên thứ tự của chúng trong bản sao sẽ được giữ nguyên. Và khi các thuộc tính của obj2được sao chép vào bản sao, vì các thuộc tính đã tồn tại obj1sẽ bị ghi đè lên, các lệnh của chúng trong bản sao sẽ được giữ nguyên.


11
Tôi không nghĩ việc bảo quản đơn hàng được đảm bảo trên các trình duyệt / công cụ.
Jo Liss

@JoLiss Trích dẫn cần thiết;) Tôi nhớ thử nghiệm điều này trong nhiều trình duyệt, nhận được kết quả nhất quán. Nhưng tất nhiên, không ai có thể đảm bảo hành vi vẫn như cũ trong các trình duyệt / công cụ trong tương lai. Đây là một mẹo (như đã được gọi ra trong câu trả lời), và tôi không có ý nói đó là một cách chắc chắn để so sánh các đối tượng.
Ates Goral

1
Chắc chắn, đây là một số gợi ý: Thông số ECMAScript cho biết đối tượng là "không có thứ tự" ; và câu trả lời này cho hành vi chuyển hướng thực tế trên các trình duyệt hiện tại.
Jo Liss

2
@JoLiss Cảm ơn vì điều đó! Nhưng xin lưu ý rằng tôi không bao giờ yêu cầu giữ trật tự giữa mã và đối tượng được biên dịch. Tôi đã yêu cầu bảo vệ trật tự của các tài sản có giá trị được thay thế tại chỗ. Đó là chìa khóa với giải pháp của tôi: sử dụng mixin để ghi đè lên các giá trị thuộc tính. Giả sử việc triển khai thường chọn sử dụng một số loại hashmap, chỉ thay thế các giá trị sẽ duy trì thứ tự các khóa. Thực tế chính xác là điều này mà tôi đã thử nghiệm trên các trình duyệt khác nhau.
Ates Goral

1
@AtesGoral: có thể làm cho ràng buộc này rõ ràng hơn một chút (in đậm, ...). Hầu hết mọi người chỉ đơn giản là sao chép-dán mà không cần đọc văn bản xung quanh nó ...
Willem Van Onsem

21

Trong Node.js, bạn có thể sử dụng nguồn gốc của nó require("assert").deepStrictEqual. Thông tin thêm: http://nodejs.org/api/assert.html

Ví dụ:

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

Một ví dụ khác trả về true/ falsethay vì trả về lỗi:

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

Chaicó tính năng này quá. Trong trường hợp của nó, bạn sẽ sử dụng:var foo = { a: 1 }; var bar = { a: 1 }; expect(foo).to.deep.equal(bar); // true;
Folusho Oladipo

Một số phiên bản của Node.js được đặt error.namethành "AssertionError [ERR_ASSERTION]". Trong trường hợp này, tôi sẽ thay thế câu lệnh if bằng if (error.code === 'ERR_ASSERTION') {.
Knute Knudsen

Tôi không có ý tưởng deepStrictEquallà con đường để đi. Tôi đã vắt óc cố gắng tìm hiểu tại sao strictEqualnó không hoạt động. Tuyệt diệu.
NetOperator Wibby

19

Các giải pháp đơn giảnhợp lý để so sánh mọi thứ như Object, Array, String, Int ...

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

Ghi chú:

  • bạn cần thay thế val1val2với đối tượng của bạn
  • đối với đối tượng, bạn phải sắp xếp đệ quy (theo khóa) cho cả hai đối tượng bên

6
Tôi giả định rằng điều này sẽ không hoạt động trong nhiều trường hợp vì thứ tự các khóa trong các đối tượng không thành vấn đề - trừ khi JSON.stringifysắp xếp theo thứ tự bảng chữ cái? (Mà tôi không thể tìm thấy tài liệu .)
Bram Vanroy

yup bạn đúng ... đối với đối tượng, bạn phải sắp xếp đệ quy cho cả hai đối tượng bên
Pratik Bhalodiya

2
Điều này không hoạt động đối với các đối tượng có tham chiếu vòng tròn
Nate-Bit Int

13

Tôi sử dụng comparablehàm này để tạo các bản sao của các đối tượng có thể so sánh 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>

Có ích trong các bài kiểm tra (hầu hết các khung kiểm tra có ischức năng). Ví dụ

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

Nếu một sự khác biệt được bắt gặp, các chuỗi được ghi lại, làm cho sự khác biệt có thể lan tỏa:

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
ý tưởng tốt (trong trường hợp của tôi, các đối tượng được so sánh chỉ là cặp khóa / giá trị, không có điều gì đặc biệt)
mech

10

Đây là một giải pháp trong ES6 / ES2015 bằng cách sử dụng phương pháp theo kiểu chức năng:

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

bản demo có sẵn ở đây


Không hoạt động nếu thứ tự của các khóa đối tượng đã thay đổi.
Isaac Pak

9

Tôi không biết có ai đăng bất cứ thứ gì tương tự như thế này không, nhưng đây là một chức năng tôi đã thực hiện để kiểm tra sự công bằng của đối tượng.

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

Ngoài ra, nó là đệ quy, vì vậy nó cũng có thể kiểm tra sự bình đẳng sâu sắc, nếu đó là những gì bạn gọi nó.


chỉnh sửa nhỏ: trước khi đi qua từng đạo cụ trong a và b, hãy thêm kiểm tra này nếu (Object.getOwnPropertyNames (a) .length! == Object.getOwnPropertyNames (b) .length) trả về sai
Hith

1
rõ ràng là kiểm tra bình đẳng thích hợp phải được đệ quy. Tôi nghĩ rằng một trong những câu trả lời đệ quy như vậy nên là câu trả lời đúng. Câu trả lời được chấp nhận không cung cấp mã và nó không có ích
canbax

9

Đối với những bạn sử dụng NodeJS, có một phương thức thuận tiện được gọi isDeepStrictEqualtrên thư viện Util bản địa có thể đạt được điều này.

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_ thẩmeepstrictequal_val1_val2


hiệu suất của nó được cho là tốt. Đừng lo lắng. Ngay cả tôi cũng sử dụng điều này trong các tình huống phức tạp. Như khi chúng ta sử dụng, chúng ta không phải lo lắng nếu thuộc tính của đối tượng mang đối tượng hoặc mảng. Json.Stringify làm cho chuỗi đó bằng mọi cách và so sánh các chuỗi trong javascript không phải là vấn đề lớn
TrickOrTreat

6

ES6: Mã tối thiểu tôi có thể hoàn thành, là đây. Nó thực hiện so sánh sâu đệ quy bằng cách xâu chuỗi tất cả các đối tượng, hạn chế duy nhất là không có phương thức hoặc biểu tượng nào được so sánh.

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


Đây là một câu trả lời đầy đủ chức năng, cảm ơn @Adriano Spadoni. Bạn có biết làm thế nào tôi có thể nhận được khóa / thuộc tính đã được sửa đổi? Cảm ơn,
Digitai

1
hi @digital, nếu bạn cần khóa nào khác, đây không phải là chức năng lý tưởng. Kiểm tra câu trả lời khác và sử dụng một với một vòng lặp thông qua các đối tượng.
Adriano Spadoni

5

bạn có thể dùng _.isEqual(obj1, obj2) từ thư viện underscore.js.

Đây là một ví dụ:

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

Xem tài liệu chính thức từ đây: http://underscorejs.org/#isEqual


5

Giả sử rằng thứ tự của các thuộc tính trong đối tượng không bị thay đổi.

JSON.opesify () hoạt động cho cả hai loại đối tượng sâu và không sâu, không chắc chắn về các khía cạnh hiệu suất:

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
Điều này không làm những gì OP muốn, vì nó sẽ chỉ khớp nếu cả hai đối tượng có tất cả các khóa giống nhau, mà chúng nói rằng chúng sẽ không. Nó cũng sẽ yêu cầu các khóa phải theo cùng một thứ tự, điều này cũng không thực sự hợp lý.
SpeedOfRound

1
Các thuộc tính là gì theo thứ tự khác nhau ??? Không phải là một phương pháp tốt
Vishal Sakaria

4

Một giải pháp đơn giản cho vấn đề này mà nhiều người không nhận ra là sắp xếp các chuỗi JSON (trên mỗi ký tự). Điều này cũng thường nhanh hơn các giải pháp khác được đề cập ở đây:

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

Một điều hữu ích khác về phương pháp này là bạn có thể lọc các so sánh bằng cách chuyển một hàm "thay thế" cho các hàm JSON.opesify ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON / stringify # example_of_USE_Vplacer_parameter ). Sau đây sẽ chỉ so sánh tất cả các khóa đối tượng được đặt tên là "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;
});

1
Ồ, tôi cũng quên, nhưng chức năng có thể được tăng tốc bởi đối tượng thử nghiệm đầu tiên cân bằng và bảo lãnh sớm nếu chúng là cùng một đối tượng: if (obj1 === obj2) trả về true;
th317erd

11
areEqual({a: 'b'}, {b: 'a'})được truerồi sao?
okm

Phải, tôi nhận ra sau khi đăng rằng "giải pháp" này có vấn đề. Nó cần thêm một chút công việc trong thuật toán sắp xếp để thực sự hoạt động đúng.
th317erd

4

Chỉ muốn đóng góp phiên bản đối tượng của tôi so sánh sử dụng một số tính năng es6. Nó không có một đơn đặt hàng vào tài khoản. Sau khi chuyển đổi tất cả if / other thành ternary, tôi đã thực hiện theo:

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

Cần một chức năng so sánh đối tượng chung chung hơn đã được đăng, tôi đã nấu chín như sau. Phê bình đánh giá cao ...

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
Tôi đã kết thúc bằng cách sử dụng một biến thể của điều này. Cảm ơn ý tưởng đếm thành viên!
NateS

2
Hãy cẩn thận về việc sửa đổi Object.prototype- trong phần lớn các trường hợp không nên sử dụng (ví dụ, các bổ sung xuất hiện trong tất cả các vòng lặp for..in chẳng hạn). Có lẽ cân nhắc Object.equals = function(aObj, bObj) {...}?
Roy Tinker

3

Nếu bạn đang so sánh các đối tượng JSON, bạn có thể sử dụng https://github.com/mirek/node-rus-diff

npm install rus-diff

Sử dụng:

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

Nếu hai đối tượng khác nhau, một {$rename:{...}, $unset:{...}, $set:{...}}đối tượng tương thích MongoDB được trả về.


3

Tôi đã đối mặt với cùng một vấn đề và quyết định viết giải pháp của riêng tôi. Nhưng vì tôi cũng muốn so sánh Mảng với các Đối tượng và ngược lại, tôi đã tạo ra một giải pháp chung. Tôi quyết định thêm các chức năng vào nguyên mẫu, nhưng người ta có thể dễ dàng viết lại chúng thành các chức năng độc lập. Đây là mã:

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

Thuật toán này được chia thành hai phần; Hàm bằng chính nó và một hàm để tìm chỉ số số của một thuộc tính trong một mảng / đối tượng. Hàm find chỉ cần thiết vì indexof chỉ tìm thấy số và chuỗi và không có đối tượng.

Người ta có thể gọi nó như thế này:

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

Hàm này trả về true hoặc false, trong trường hợp này là true. Thuật toán als cho phép so sánh giữa các đối tượng rất phức tạp:

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

Ví dụ trên sẽ trả về true, thậm chí tho các thuộc tính có thứ tự khác. Một chi tiết nhỏ cần chú ý: Mã này cũng kiểm tra cùng loại hai biến, vì vậy "3" không giống với 3.


3

Tôi thấy câu trả lời mã spaghetti. Không sử dụng bất kỳ libs của bên thứ ba, điều này rất dễ dàng.

Đầu tiên, sắp xếp hai đối tượng theo tên khóa của chúng.

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)

Sau đó, chỉ cần sử dụng một chuỗi để so sánh chúng.

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

Điều này cũng không hoạt động cho bản sao sâu, nó chỉ có một độ sâu lặp.
andras

Tôi nghĩ @andras có nghĩa là bạn cần sắp xếp đệ quy các khóa của các đối tượng lồng nhau.
Davi Lima

2

Tôi khuyên bạn không nên băm hoặc tuần tự hóa (như giải pháp JSON đề xuất). Nếu bạn cần kiểm tra xem hai đối tượng có bằng nhau không, thì bạn cần xác định nghĩa của bằng nhau là gì. Có thể là tất cả các thành viên dữ liệu trong cả hai đối tượng đều khớp hoặc có thể phải khớp vị trí bộ nhớ (nghĩa là cả hai biến tham chiếu cùng một đối tượng trong bộ nhớ) hoặc có thể chỉ một thành viên dữ liệu trong mỗi đối tượng phải khớp.

Gần đây tôi đã phát triển một đối tượng mà hàm tạo của nó tạo một id mới (bắt đầu từ 1 và tăng thêm 1) mỗi khi một thể hiện được tạo. Đối tượng này có hàm isEqual so sánh giá trị id đó với giá trị id của đối tượng khác và trả về true nếu chúng khớp.

Trong trường hợp đó, tôi đã định nghĩa "bằng" có nghĩa là các giá trị id khớp. Cho rằng mỗi phiên bản có một id duy nhất, điều này có thể được sử dụng để thực thi ý tưởng rằng các đối tượng phù hợp cũng chiếm cùng một vị trí bộ nhớ. Mặc dù điều đó là không cần thiết.


2

Thật hữu ích khi xem xét hai đối tượng bằng nhau nếu chúng có tất cả các giá trị giống nhau cho tất cả các thuộc tính và đệ quy cho tất cả các đối tượng và mảng được lồng. Tôi cũng xem xét hai đối tượng sau bằng nhau:

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

Tương tự, các mảng có thể có các phần tử "thiếu" và các phần tử không xác định. Tôi cũng sẽ đối xử với những người như vậy:

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

Một hàm thực hiện định nghĩa về đẳng thức này:

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

Mã nguồn (bao gồm các hàm của trình trợ giúp, generalType và uniqueArray): Kiểm tra đơn vịchạy thử tại đây .


2

Tôi đang thực hiện các giả định sau với chức năng này:

  1. Bạn điều khiển các đối tượng bạn đang so sánh và bạn chỉ có các giá trị nguyên thủy (nghĩa là không phải các đối tượng lồng nhau, các hàm, v.v.).
  2. Trình duyệt của bạn có hỗ trợ cho Object.keys .

Điều này nên được coi là một minh chứng cho một chiến lược đơn giản.

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

Đây là một bổ sung cho tất cả các bên trên, không phải là một thay thế. Nếu bạn cần nhanh chóng so sánh các đối tượng nông mà không cần kiểm tra thêm các trường hợp đệ quy. Đây là một shot.

Điều này so sánh với: 1) Bình đẳng về số lượng thuộc tính riêng, 2) Bình đẳng về tên khóa, 3) nếu bCompareValues ​​== true, Bình đẳng của các giá trị thuộc tính tương ứng và các loại của chúng (ba đẳng thức)

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

Để so sánh các khóa cho các trường hợp đối tượng cặp khóa / giá trị đơn giản, tôi sử dụng:

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

Khi các khóa được so sánh, một bổ sung đơn giản for..in vòng lặp là đủ.

Độ phức tạp là O (N * N) với N là số lượng khóa.

Tôi hy vọng / đoán các đối tượng tôi xác định sẽ không nắm giữ hơn 1000 thuộc tính ...


2

Tôi biết điều này hơi cũ, nhưng tôi muốn thêm một giải pháp mà tôi đã đưa ra cho vấn đề này. Tôi đã có một đối tượng và tôi muốn biết khi nào dữ liệu của nó thay đổi. "Một cái gì đó tương tự như Object.observe" và những gì tôi đã làm là:

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

Điều này ở đây có thể được nhân đôi và tạo ra một tập hợp các mảng khác để so sánh các giá trị và khóa. Nó rất đơn giản vì bây giờ chúng là mảng và sẽ trả về false nếu các đối tượng có kích thước khác nhau.


Điều này sẽ luôn trả về false, vì các mảng không so sánh theo giá trị, ví dụ [1,2] != [1,2].
jib
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.