使用 jQuery 比较两个 Javascript 对象数组

我有两个 JavaScript 对象数组,我想比较看看它们是否相同。每个数组中的对象可能不是(而且很可能不是)相同的顺序。每个数组不应该有超过10个对象。我认为 jQuery 可能对这个问题有一个优雅的解决方案,但是我在网上找不到太多。

我知道一个粗糙的嵌套 $.each(array, function(){})解决方案可以工作,但是有没有内置的功能,我不知道?

谢谢。

133465 次浏览

I don't think there's a good "jQuery " way to do this, but if you need efficiency, map one of the arrays by a certain key (one of the unique object fields), and then do comparison by looping through the other array and comparing against the map, or associative array, you just built.

If efficiency is not an issue, just compare every object in A to every object in B. As long as |A| and |B| are small, you should be okay.

Well, if you want to compare only the contents of arrays, there's a useful jQuery function $.inArray()

var arr = [11, "String #1", 14, "String #2"];
var arr_true = ["String #1", 14, "String #2", 11]; // contents are the same as arr
var arr_false = ["String #1", 14, "String #2", 16]; // contents differ


function test(arr_1, arr_2) {
var equal = arr_1.length == arr_2.length; // if array sizes mismatches, then we assume, that they are not equal
if (equal) {
$.each(arr_1, function (foo, val) {
if (!equal) return false;
if ($.inArray(val, arr_2) == -1) {
equal = false;
} else {
equal = true;
}
});
}
return equal;
}


alert('Array contents are the same? ' + test(arr, arr_true)); //- returns true
alert('Array contents are the same? ' + test(arr, arr_false)); //- returns false

I was also looking for this today and found: http://www.breakingpar.com/bkp/home.nsf/0/87256B280015193F87256BFB0077DFFD

Don't know if that's a good solution though they do mention some performance considerations taken into account.

I like the idea of a jQuery helper method. @David I'd rather see your compare method to work like:

jQuery.compare(a, b)

I doesn't make sense to me to be doing:

$(a).compare(b)

where a and b are arrays. Normally when you $(something) you'd be passing a selector string to work with DOM elements.

Also regarding sorting and 'caching' the sorted arrays:

  • I don't think sorting once at the start of the method instead of every time through the loop is 'caching'. The sort will still happen every time you call compare(b). That's just semantics, but...
  • for (var i = 0; t[i]; i++) { ...this loop finishes early if your t array contains a false value in it somewhere, so $([1, 2, 3, 4]).compare([1, false, 2, 3]) returns true!
  • More importantly the array sort() method sorts the array in place, so doing var b = t.sort() ...doesn't create a sorted copy of the original array, it sorts the original array and also assigns a reference to it in b. I don't think the compare method should have side-effects.

It seems what we need to do is to copy the arrays before working on them. The best answer I could find for how to do that in a jQuery way was by none other than John Resig here on SO! What is the most efficient way to deep clone an object in JavaScript? (see comments on his answer for the array version of the object cloning recipe)

In which case I think the code for it would be:

jQuery.extend({
compare: function (arrayA, arrayB) {
if (arrayA.length != arrayB.length) { return false; }
// sort modifies original array
// (which are passed by reference to our method!)
// so clone the arrays before sorting
var a = jQuery.extend(true, [], arrayA);
var b = jQuery.extend(true, [], arrayB);
a.sort();
b.sort();
for (var i = 0, l = a.length; i < l; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
});


var a = [1, 2, 3];
var b = [2, 3, 4];
var c = [3, 4, 2];


jQuery.compare(a, b);
// false


jQuery.compare(b, c);
// true


// c is still unsorted [3, 4, 2]

I found this discussion because I needed a way to deep compare arrays and objects. Using the examples here, I came up with the following (broken up into 3 methods for clarity):

jQuery.extend({
compare : function (a,b) {
var obj_str = '[object Object]',
arr_str = '[object Array]',
a_type  = Object.prototype.toString.apply(a),
b_type  = Object.prototype.toString.apply(b);


if ( a_type !== b_type) { return false; }
else if (a_type === obj_str) {
return $.compareObject(a,b);
}
else if (a_type === arr_str) {
return $.compareArray(a,b);
}
return (a === b);
}
});


jQuery.extend({
compareArray: function (arrayA, arrayB) {
var a,b,i,a_type,b_type;
// References to each other?
if (arrayA === arrayB) { return true;}


if (arrayA.length != arrayB.length) { return false; }
// sort modifies original array
// (which are passed by reference to our method!)
// so clone the arrays before sorting
a = jQuery.extend(true, [], arrayA);
b = jQuery.extend(true, [], arrayB);
a.sort();
b.sort();
for (i = 0, l = a.length; i < l; i+=1) {
a_type = Object.prototype.toString.apply(a[i]);
b_type = Object.prototype.toString.apply(b[i]);


if (a_type !== b_type) {
return false;
}


if ($.compare(a[i],b[i]) === false) {
return false;
}
}
return true;
}
});


jQuery.extend({
compareObject : function(objA,objB) {


var i,a_type,b_type;


// Compare if they are references to each other
if (objA === objB) { return true;}


if (Object.keys(objA).length !== Object.keys(objB).length) { return false;}
for (i in objA) {
if (objA.hasOwnProperty(i)) {
if (typeof objB[i] === 'undefined') {
return false;
}
else {
a_type = Object.prototype.toString.apply(objA[i]);
b_type = Object.prototype.toString.apply(objB[i]);


if (a_type !== b_type) {
return false;
}
}
}
if ($.compare(objA[i],objB[i]) === false){
return false;
}
}
return true;
}
});

Testing

var a={a : {a : 1, b: 2}},
b={a : {a : 1, b: 2}},
c={a : {a : 1, b: 3}},
d=[1,2,3],
e=[2,1,3];


console.debug('a and b = ' + $.compare(a,b)); // a and b = true
console.debug('b and b = ' + $.compare(b,b)); // b and b = true
console.debug('b and c = ' + $.compare(b,c)); // b and c = false
console.debug('c and d = ' + $.compare(c,d)); // c and d = false
console.debug('d and e = ' + $.compare(d,e)); // d and e = true

There is an easy way...

$(arr1).not(arr2).length === 0 && $(arr2).not(arr1).length === 0

If the above returns true, both the arrays are same even if the elements are in different order.

NOTE: This works only for jquery versions < 3.0.0 when using JSON objects

Try this

function check(c,d){
var a = c, b = d,flg = 0;
if(a.length == b.length)
{
for(var i=0;i<a.length;i++)
a[i] != b[i] ? flg++ : 0;
}
else
{
flg = 1;
}
return flg = 0;
}

The nice one liner from Sudhakar R as jQuery global method.

/**
* Compare two arrays if they are equal even if they have different order.
*
* @link https://stackoverflow.com/a/7726509
*/
jQuery.extend({
/**
* @param {array} a
*   First array to compare.
* @param {array} b
*   Second array to compare.
* @return {boolean}
*   True if both arrays are equal, otherwise false.
*/
arrayCompare: function (a, b) {
return $(a).not(b).get().length === 0 && $(b).not(a).get().length === 0;
}
});

My approach was quite different - I flattened out both collections using JSON.stringify and used a normal string compare to check for equality.

I.e.

var arr1 = [
{Col: 'a', Val: 1},
{Col: 'b', Val: 2},
{Col: 'c', Val: 3}
];


var arr2 = [
{Col: 'x', Val: 24},
{Col: 'y', Val: 25},
{Col: 'z', Val: 26}
];


if(JSON.stringify(arr1) == JSON.stringify(arr2)){
alert('Collections are equal');
}else{
alert('Collections are not equal');
}

NB: Please note that his method assumes that both Collections are sorted in a similar fashion, if not, it would give you a false result!

I also found this when looking to do some array comparisons with jQuery. In my case I had strings which I knew to be arrays:

var needle = 'apple orange';
var haystack = 'kiwi orange banana apple plum';

But I cared if it was a complete match or only a partial match, so I used something like the following, based off of Sudhakar R's answer:

function compareStrings( needle, haystack ){
var needleArr = needle.split(" "),
haystackArr = haystack.split(" "),
compare = $(haystackArr).not(needleArr).get().length;


if( compare == 0 ){
return 'all';
} else if ( compare == haystackArr.length  ) {
return 'none';
} else {
return 'partial';
}
}

In my case compared arrays contain only numbers and strings. This solution worked for me:

function are_arrs_equal(arr1, arr2){
return arr1.sort().toString() === arr2.sort().toString()
}

Let's test it!

arr1 = [1, 2, 3, 'nik']
arr2 = ['nik', 3, 1, 2]
arr3 = [1, 2, 5]


console.log (are_arrs_equal(arr1, arr2)) //true
console.log (are_arrs_equal(arr1, arr3)) //false

Change array to string and compare

var arr = [1,2,3],
arr2 = [1,2,3];
console.log(arr.toString() === arr2.toString());

Convert both array to string and compare

if (JSON.stringify(array1) == JSON.stringify(array2))
{
// your code here
}

If duplicates matter such that [1, 1, 2] should not be equal to [2, 1] but should equal [1, 2, 1], here is a reference counting solution:

  const arrayContentsEqual = (arrayA, arrayB) => {
if (arrayA.length !== arrayB.length) {
return false}


const refCount = (function() {
const refCountMap = {};
const refCountFn = (elt, count) => {
refCountMap[elt] = (refCountMap[elt] || 0) + count}
refCountFn.isZero = () => {
for (let elt in refCountMap) {
if (refCountMap[elt] !== 0) {
return false}}
return true}
return refCountFn})()


arrayB.map(eltB => refCount(eltB, 1));
arrayA.map(eltA => refCount(eltA, -1));
return refCount.isZero()}

Here is the fiddle to play with.

var arr1 = [
{name: 'a', Val: 1},
{name: 'b', Val: 2},
{name: 'c', Val: 3}
];


var arr2 = [
{name: 'c', Val: 3},
{name: 'x', Val: 4},
{name: 'y', Val: 5},
{name: 'z', Val: 6}
];
var _isEqual = _.intersectionWith(arr1, arr2, _.isEqual);// common in both array
var _difference1 = _.differenceWith(arr1, arr2, _.isEqual);//difference from array1
var _difference2 = _.differenceWith(arr2, arr1, _.isEqual);//difference from array2
console.log(_isEqual);// common in both array
console.log(_difference1);//difference from array1
console.log(_difference2);//difference from array2 
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.js"></script>