如何使用JavaScript检查两个数组是否相等?

421
var a = [1, 2, 3];
var b = [3, 2, 1];
var c = new Array(1, 2, 3);

alert(a == b + "|" + b == c);

演示

如何检查这些数组是否相等并获得一个返回 true 的方法?

jQuery 是否提供了任何方法用于此?


17
这个例子似乎暗示我们不应该考虑数字的顺序([1,2,3] == [3,2,1])。然而,一个没有解决这个问题的答案被标记为正确,而唯一解决了这个问题的答案(使用sort())则被踩了。如果顺序无关紧要,那么这个例子需要进行修正。否则,标记为正确的答案就不能算正确。 - Stefan Haustein
难道最简单的浅层比较两个“数字”对象的方法不是像这样做吗?a.every((v,i)= > v === b [i]) b.every((v,i)= > v === c [i]) - RavenHursT
12
在 ES6 中,只需执行 a.every(item => b.includes(item)) && b.every(item => a.includes(item))。不要忘记检查双方,因为如果你只执行一个 every,你会处理这种情况:a=[3,3,3], b=[1,2,3] 而导致结果为假。 - NoxFly
@NoxFly的建议很好,但只适用于您不关心顺序并且要么检查长度要么不关心重复项的情况。否则[1,2,3]将等于[1,3,2]并且等于[1,2,3,3] - Richard Wheeldon
16个回答

419

以下是你应该做的。请不要使用stringify<>

function arraysEqual(a, b) {
  if (a === b) return true;
  if (a == null || b == null) return false;
  if (a.length !== b.length) return false;

  // If you don't care about the order of the elements inside
  // the array, you should sort both arrays here.
  // Please note that calling sort on an array will modify that array.
  // you might want to clone your array first.

  for (var i = 0; i < a.length; ++i) {
    if (a[i] !== b[i]) return false;
  }
  return true;
}

21
这不是我写的代码,但编写它的人不想发布。 - enyo
12
如果数组包含对象,这将无法起作用。 - GijsjanB
24
如果数组包含相同的对象,它确实有效。当然,如果对象不相同并且您想要比较对象本身,则不起作用。但那不是问题的关键。 - enyo
16
为什么我们不应该使用stringify< >?请在您的回答中解释原因。 为了避免XSS攻击,我们不应该使用stringify< >。这些字符可以在Web应用程序中嵌入恶意代码,并使攻击者能够访问用户的敏感数据。相反,我们应该使用安全的HTML编码技术来转义输入并保护应用程序的安全性。 - Jonathan Benn
21
过了7年后... 可以使用一些性感的箭头函数来代替那个for循环... return a.every((val, idx) => val === b[idx]) - Mr5o1
显示剩余18条评论

276

[2021更新日志:修复选项4的错误:不支持js对象的总排序(即使排除NaN!=NaN'5'==5'5'===5'2'<3等)),因此不能在Map.keys()上使用.sort(cmpFunc)(尽管您可以在Object.keys(obj)上使用,因为即使是“数字”键也是字符串)。

选项1

最简单的选项,在几乎所有情况下都有效,但是null!==undefined,但它们都被转换为JSON表示形式null并被认为是相等的:

function arraysEqual(a1,a2) {
    /* WARNING: arrays must not contain {objects} or behavior may be undefined */
    return JSON.stringify(a1)==JSON.stringify(a2);
}

如果您的数组包含对象,则可能无法正常工作。是否仍可使用对象取决于JSON实现是否对键进行排序。例如,{1:2,3:4}的JSON可能等于或不等于{3:4,1:2};这取决于实现,规范没有任何保证。[2017更新:实际上,ES6规范现在保证对象键将按照1)整数属性的顺序,2)按照定义的顺序排列的属性,然后是3)按照定义的顺序排列的符号属性的顺序迭代。因此,如果JSON.stringify实现遵循此方式,则相等的对象(在===意义上但不一定是==意义上)将字符串化为相等的值。需要更多研究。因此,我想您可以制作一个具有相反顺序属性的对象的邪恶克隆,但我无法想象它会意外发生...]至少在Chrome上,JSON.stringify函数倾向于按定义的顺序返回键(至少我注意到了这一点),但是这种行为完全可能在任何时候发生变化,不应依赖于此。如果您选择不在列表中使用对象,则应该可以正常工作。如果您的列表中有具有唯一ID的所有对象,则可以执行a1.map(function(x)}{return {id:x.uniqueId}})。如果您的列表中有任意对象,则可以继续阅读选项#2。
这也适用于嵌套数组。
然而,由于创建这些字符串和进行垃圾回收的开销,它略微效率低下。

选项2

历史版本1解决方案:

// generally useful functions
function type(x) { // does not work in general, but works on JSONable objects we care about... modify as you see fit
    // e.g.  type(/asdf/g) --> "[object RegExp]"
    return Object.prototype.toString.call(x);
}
function zip(arrays) {
    // e.g. zip([[1,2,3],[4,5,6]]) --> [[1,4],[2,5],[3,6]]
    return arrays[0].map(function(_,i){
        return arrays.map(function(array){return array[i]})
    });
}


// helper functions
function allCompareEqual(array) {
    // e.g.  allCompareEqual([2,2,2,2]) --> true
    // does not work with nested arrays or objects
    return array.every(function(x){return x==array[0]});
}

function isArray(x){ return type(x)==type([]) }
function getLength(x){ return x.length }
function allTrue(array){ return array.reduce(function(a,b){return a&&b},true) }
    // e.g. allTrue([true,true,true,true]) --> true
    // or just array.every(function(x){return x});


function allDeepEqual(things) {
    // works with nested arrays
    if( things.every(isArray) )
        return allCompareEqual(things.map(getLength))     // all arrays of same length
               && allTrue(zip(things).map(allDeepEqual)); // elements recursively equal

    //else if( this.every(isObject) )
    //  return {all have exactly same keys, and for 
    //          each key k, allDeepEqual([o1[k],o2[k],...])}
    //  e.g. ... && allTrue(objectZip(objects).map(allDeepEqual)) 

    //else if( ... )
    //  extend some more

    else
        return allCompareEqual(things);
}

// Demo:

allDeepEqual([ [], [], [] ])
true
allDeepEqual([ [1], [1], [1] ])
true
allDeepEqual([ [1,2], [1,2] ])
true
allDeepEqual([ [[1,2],[3]], [[1,2],[3]] ])
true

allDeepEqual([ [1,2,3], [1,2,3,4] ])
false
allDeepEqual([ [[1,2],[3]], [[1,2],[],3] ])
false
allDeepEqual([ [[1,2],[3]], [[1],[2,3]] ])
false
allDeepEqual([ [[1,2],3], [1,[2,3]] ])
false
<!--

More "proper" option, which you can override to deal with special cases (like regular objects and null/undefined and custom objects, if you so desire):

To use this like a regular function, do:

    function allDeepEqual2() {
        return allDeepEqual([].slice.call(arguments));
    }

Demo:

    allDeepEqual2([[1,2],3], [[1,2],3])
    true
    
  -->


选项3

function arraysEqual(a,b) {
    /*
        Array-aware equality checker:
        Returns whether arguments a and b are == to each other;
        however if they are equal-lengthed arrays, returns whether their 
        elements are pairwise == to each other recursively under this
        definition.
    */
    if (a instanceof Array && b instanceof Array) {
        if (a.length!=b.length)  // assert same length
            return false;
        for(var i=0; i<a.length; i++)  // assert each element equal
            if (!arraysEqual(a[i],b[i]))
                return false;
        return true;
    } else {
        return a==b;  // if not both arrays, should be the same
    }
}

//Examples:

arraysEqual([[1,2],3], [[1,2],3])
true
arraysEqual([1,2,3], [1,2,3,4])
false
arraysEqual([[1,2],[3]], [[1,2],[],3])
false
arraysEqual([[1,2],[3]], [[1],[2,3]])
false
arraysEqual([[1,2],3], undefined)
false
arraysEqual(undefined, undefined)
true
arraysEqual(1, 2)
false
arraysEqual(null, null)
true
arraysEqual(1, 1)
true
arraysEqual([], 1)
false
arraysEqual([], undefined)
false
arraysEqual([], [])
true
/*
If you wanted to apply this to JSON-like data structures with js Objects, you could do so. Fortunately we're guaranteed that all objects keys are unique, so iterate over the objects OwnProperties and sort them by key, then assert that both the sorted key-array is equal and the value-array are equal, and just recurse. We CANNOT extend the sort-then-compare method with Maps as well; even though Map keys are unique, there is no total ordering in ecmascript, so you can't sort them... but you CAN query them individually (see the next section Option 4). (Also if we extend this to Sets, we run into the tree isomorphism problem http://logic.pdmi.ras.ru/~smal/files/smal_jass08_slides.pdf - fortunately it's not as hard as general graph isomorphism; there is in fact an O(#vertices) algorithm to solve it, but it can get very complicated to do it efficiently. The pathological case is if you have a set made up of lots of seemingly-indistinguishable objects, but upon further inspection some of those objects may differ as you delve deeper into them. You can also work around this by using hashing to reject almost all cases.)
*/
<!--
**edit**: It's 2016 and my previous overcomplicated answer was bugging me. This recursive, imperative "recursive programming 101" implementation keeps the code really simple, and furthermore fails at the earliest possible point (giving us efficiency). It also doesn't generate superfluous ephemeral datastructures (not that there's anything wrong with functional programming in general, but just keeping it clean here).

If we wanted to apply this to a non-empty arrays of arrays, we could do seriesOfArrays.reduce(arraysEqual).

This is its own function, as opposed to using Object.defineProperties to attach to Array.prototype, since that would fail with a key error if we passed in an undefined value (that is however a fine design decision if you want to do so).

This only answers OPs original question.
-->


选项4:(2016年编辑的延续)

这应该适用于大多数对象:

const STRICT_EQUALITY_BROKEN = (a,b)=> a===b;
const STRICT_EQUALITY_NO_NAN = (a,b)=> {
    if (typeof a=='number' && typeof b=='number' && ''+a=='NaN' && ''+b=='NaN')
        // isNaN does not do what you think; see +/-Infinity
        return true;
    else
        return a===b;
};
function deepEquals(a,b, areEqual=STRICT_EQUALITY_NO_NAN, setElementsAreEqual=STRICT_EQUALITY_NO_NAN) {
    /* compares objects hierarchically using the provided 
       notion of equality (defaulting to ===);
       supports Arrays, Objects, Maps, ArrayBuffers */
    if (a instanceof Array && b instanceof Array)
        return arraysEqual(a,b, areEqual);
    if (Object.getPrototypeOf(a)===Object.prototype && Object.getPrototypeOf(b)===Object.prototype)
        return objectsEqual(a,b, areEqual);
    if (a instanceof Map && b instanceof Map)
        return mapsEqual(a,b, areEqual);        
    if (a instanceof Set && b instanceof Set) {
        if (setElementsAreEqual===STRICT_EQUALITY_NO_NAN)
            return setsEqual(a,b);
        else
            throw "Error: set equality by hashing not implemented because cannot guarantee custom notion of equality is transitive without programmer intervention."
    }
    if ((a instanceof ArrayBuffer || ArrayBuffer.isView(a)) && (b instanceof ArrayBuffer || ArrayBuffer.isView(b)))
        return typedArraysEqual(a,b);
    return areEqual(a,b);  // see note[1] -- IMPORTANT
}

function arraysEqual(a,b, areEqual) {
    if (a.length!=b.length)
        return false;
    for(var i=0; i<a.length; i++)
        if (!deepEquals(a[i],b[i], areEqual))
            return false;
    return true;
}
function objectsEqual(a,b, areEqual) {
    var aKeys = Object.getOwnPropertyNames(a);
    var bKeys = Object.getOwnPropertyNames(b);
    if (aKeys.length!=bKeys.length)
        return false;
    aKeys.sort();
    bKeys.sort();
    for(var i=0; i<aKeys.length; i++)
        if (!areEqual(aKeys[i],bKeys[i])) // keys must be strings
            return false;
    return deepEquals(aKeys.map(k=>a[k]), aKeys.map(k=>b[k]), areEqual);
}
function mapsEqual(a,b, areEqual) { // assumes Map's keys use the '===' notion of equality, which is also the assumption of .has and .get methods in the spec; however, Map's values use our notion of the areEqual parameter
    if (a.size!=b.size)
        return false;
    return [...a.keys()].every(k=> 
        b.has(k) && deepEquals(a.get(k), b.get(k), areEqual)
    );
}
function setsEqual(a,b) {
    // see discussion in below rest of StackOverflow answer
    return a.size==b.size && [...a.keys()].every(k=> 
        b.has(k)
    );
}
function typedArraysEqual(a,b) {
    // we use the obvious notion of equality for binary data
    a = new Uint8Array(a);
    b = new Uint8Array(b);
    if (a.length != b.length)
        return false;
    for(var i=0; i<a.length; i++)
        if (a[i]!=b[i])
            return false;
    return true;
}
Demo (not extensively tested):

var nineTen = new Float32Array(2);
nineTen[0]=9; nineTen[1]=10;

> deepEquals(
    [[1,[2,3]], 4, {a:5,'111':6}, new Map([['c',7],['d',8]]), nineTen],
    [[1,[2,3]], 4, {111:6,a:5}, new Map([['d',8],['c',7]]), nineTen]
)
true

> deepEquals(
    [[1,[2,3]], 4, {a:'5','111':6}, new Map([['c',7],['d',8]]), nineTen],
    [[1,[2,3]], 4, {111:6,a:5}, new Map([['d',8],['c',7]]), nineTen],
    (a,b)=>a==b
)
true


请注意,如果使用 == 的相等概念,则需要知道虚假值和强制转换意味着 == 等式不是传递的。例如,''==00=='0',但''!='0'。这对于集合很重要:我认为不能以有意义的方式覆盖Set相等性的概念。如果使用内置的Set相等性概念(即 === ),则上述内容应该有效。但是,如果使用非传递的相等概念,比如 == ,就会遇到一些麻烦:即使你强制用户在域上定义哈希函数(hash(a)!= hash(b)意味着a!= b),我也不确定那会有所帮助...当然,你可以像气泡排序一样一对一对地删除 == 项,并进行第二个O(N ^ 2)遍历以确认等价类中的事物实际上是彼此 == ,并且!= 与未配对的所有其他事物相同,但如果有一些强制转换正在进行,仍然必须抛出运行时错误...您还可能会得到奇怪(但可能不是那么奇怪)的边缘情况,https://developer.mozilla.org/en-US/docs/Glossary/Falsy和Truthy值(除了NaN == NaN...但仅适用于Sets!)。这通常不是大多数同质数据类型集合的问题。

总结递归相等性在集合上的复杂性:

  • 集合相等性问题是树同构问题http://logic.pdmi.ras.ru/~smal/files/smal_jass08_slides.pdf的简化版
  • 假设A集合=?B集合等同于B.has(k) for every k in A,隐式使用===相等性([1,2,3] !== [1,2,3]),而不是递归相等性(deepEquals([1,2,3],[1,2,3]) == true),因此两个new Set([[1,2,3]])将不相等,因为我们没有递归
  • 如果您使用的递归相等性的概念不是1)自反(a=b意味着b=a)和2)对称(a=a)和3)传递(a=b且b=c意味着a=c);这是等价类的定义,那么尝试使递归相等性工作就有点无意义了
  • 显然,等于==运算符不遵守许多这些属性
  • 即使是ecmascript中的严格相等===运算符也不遵守这些属性,因为ecmascript的严格相等比较算法具有NaN!=NaN;这就是为什么许多本机数据类型如SetMap将NaN“等同于”将它们视为相同的值时出现为键
  • 只要我们强制并确保递归集合相等性确实是传递性,自反性和对称性,我们就可以确保不会发生任何可怕的事情。
    • 然后,我们可以通过随机递归比较所有内容来进行O(N ^ 2)比较,这非常低效。没有神奇的算法可以让我们执行setKeys.sort((a,b)=> /*some comparison function*/),因为在ecmascript中没有全序关系(''==0 and 0=='0',但''!='0'...尽管我相信您可能能够自己定义一个,这肯定是一个崇高的目标)。
    • 但是,我们可以.toStringJSON.stringify所有元素来帮助我们。然后我们将对它们进行排序,这给我们了可能为假阳性的等价类(两个相同的东西不会有相同的字符串JSON表示),(两个不同的东西可能具有相同的字符串或JSON表示)。
      • 但是,这会引入其自身的性能问题,因为重复序列化相同的内容,然后重复序列化该内容的子集非常低效。想象一下嵌套的Set树;每个节点将属于O(深度)不同的序列化!
      • 即使这不是问题,最坏情况下的性能仍然是O(N!),如果所有序列化“提示”都相同
因此,上述实现声明如果项目只是普通的 ===(不递归 ===),则集合相等。这意味着对于 new Set([1,2,3])new Set([1,2,3]) 它将返回 false。如果您知道自己在做什么,可以通过一些努力重写代码的那部分。
(附注:Maps 是 es6 字典。我不确定它们是否具有 O(1) 或 O(log(N)) 的查找性能,但无论如何它们都是“有序”的,因为它们跟踪了插入它们的键值对的顺序。然而,关于如果两个 Maps 插入元素的顺序不同,它们是否应该被视为相等的语义是模棱两可的。下面是一个深度相等的示例实现,即使元素以不同的顺序插入到其中,也会认为两个 Maps 相等。) (注[1]:重要提示:等式概念:您可能想使用自定义等式概念覆盖已注明的行,在任何出现的其他函数中也必须更改。例如,您是否希望NaN == NaN?默认情况下不是这样。甚至还有更奇怪的事情,比如0 =='0'。如果且仅当它们在内存中是相同的对象时,您是否认为两个对象是相同的?请参见https://dev59.com/KG035IYBdhLWcg3wZvFg#5447170。您应该记录您使用的等式概念。)还要注意,其他简单地使用.toString.sort的答案有时会遭受到0!= -0但几乎所有数据类型和JSON序列化都被视为相等和可规范化为0的事实;-0==0 在您的等式概念中也应该被记录,以及那个表中的大多数其他事项,例如NaN等。 您应该能够将上述内容扩展到WeakMaps、WeakSets。不确定是否有意义将其扩展到DataViews。可能也可以将其扩展到RegExps等。在扩展时,您会意识到进行了许多不必要的比较。这就是我早期定义的“type”函数(解决方案#2)可以派上用场的地方;然后您可以立即分派。是否值得(可能?不确定它在幕后如何工作)使用字符串表示类型的开销取决于您。然后,您可以重写分派器,即函数“deepEquals”,使其类似于:
var dispatchTypeEquals = {
    number: function(a,b) {...a==b...},
    array: function(a,b) {...deepEquals(x,y)...},
    ...
}
function deepEquals(a,b) {
    var typeA = extractType(a);
    var typeB = extractType(a);
    return typeA==typeB && dispatchTypeEquals[typeA](a,b);
}

2
+1,一个小注释:对于allTrue,您也可以使用返回数组元素值的函数与array.every一起使用。 - pimvdb
2
JSON.stringify(null) === 'null'(字符串“null”),而不是null - djechlin
1
“undefined” 没有被转换为字符串 “null” … 你能否花一分钟时间了解 JSON.stringify 如何工作? - djechlin
哎呀,看来我当时匆忙之下误读了某人的评论,因此说了一个无关的谎言(即JSON.stringify(null)=='"null"')。但是,就我回答中未提及的任何未定义或空值而言,我仍然没有看到与OP问题相关的问题。因此,我坚持我的答案及其演示测试。 - ninjagecko
JSON.stringify不能保证每次都将一个对象转换为相同的字符串。 - runspired
显示剩余6条评论

97

对于像数字和字符串这样的原始值,这是一个简单的解决方案:

a = [1,2,3]

b = [3,2,1]

a.sort().toString() == b.sort().toString() 

调用 sort() 方法将确保元素的顺序不重要。调用 toString() 方法将创建一个以逗号分隔值的字符串,因此可以测试这两个字符串是否相等。


8
如果您的数组包含除简单值以外的任何内容,请小心。Array.prototype.sort()是浅层排序,Array.prototype.toString()将对象转换为[object Object]并扁平化任何嵌套的数组,这可能导致假阳性。 - Nate
7
我不明白为什么这个被踩了:这是目前唯一一个能够正确解决(未明确说明的)例子的解决方案... - Stefan Haustein
17
a.sort() 不仅仅返回排序后的数组,它会改变原始数组本身,这可能会对应用程序产生意想不到的影响。 - pilat
4
你错了。[12, 34, 56].toString() 的结果是 "12,34,56" ,而 [1, 23, 456].toString() 的结果是 "1,23,456",因此它们不相等。 - Must Ckr
6
没错,但问题仍然存在:['1,2',3].toString() === [1,'2,3'].toString() 是一个错误的匹配。 - feihcsim
显示剩余5条评论

96

jQuery没有比较数组的方法。不过Underscore库(或者类似的Lodash库)提供了这样一个方法:isEqual。它还可以处理其他各种情况(比如对象字面量)。为了遵循提供的例子:

var a=[1,2,3];
var b=[3,2,1];
var c=new Array(1,2,3);

alert(_.isEqual(a, b) + "|" + _.isEqual(b, c));

顺便提一下:Underscore 还有很多其他 jQuery 没有的方法,所以它是 jQuery 的很好补充。

编辑: 正如评论中指出的那样,上述方法只在两个数组的元素顺序相同时有效,即:

_.isEqual([1,2,3], [1,2,3]); // true
_.isEqual([1,2,3], [3,2,1]); // false

幸运的是,JavaScript 提供了一种内置方法来解决这个问题,sort

_.isEqual([1,2,3].sort(), [3,2,1].sort()); // true

3
Underscorejs 是我日常使用的库。它的简单易用性绝对应该被高度评价。 - Jan Stanicek
Underscore已被一个更优秀的库lodash替代。它是Underscore的超集,也支持_.isEqual(a, b)。查看此链接获取更多细节。 - Kunal Kapadia
2
我喜欢 Lodash,它包含了 Underscore 的超集……但它并不是“更优秀的”,也没有“取代”Underscore。首先,Lodash 已经被元编程和微观优化的程度使得其源代码基本上无法阅读。当你(比如)不小心传递了错误的参数到一个 Lodash/Underscore 函数中,并需要调试时,这就很重要了。在那种情况下,Underscore 就远远优于 Lodash,因为(与 Lodash 不同),你可以实际阅读源代码。最终,两个库都没有优于另一个,它们只是有不同的长处和短处。 - machineghost
顺便提一下,这两个库的作者(John-David Dalton和Jeremy Ashkenas)最近讨论了合并它们的前景,因为它们有共同点。但是(如果您阅读GitHub线程https://github.com/jashkenas/underscore/issues/2182,您会发现),这不是一个明显的决定。事实上,据我所知,自那以后已经过去了3个多月,没有做出任何决定,正是因为Underscore具有可读性强的源代码等优势,Underscore用户不想失去这些优势。 - machineghost
2
回到正题,这里的警报是 false|false_.isEqual(a,b) 根据它们的顺序比较数组的元素,因此如果需要进行无序比较,则必须在比较之前对数组进行排序。 - Paul
显示剩余4条评论

38

使用 JavaScript 1.6 版本就像这样简单:

Array.prototype.equals = function( array ) {
  return this.length == array.length && 
         this.every( function(this_i,i) { return this_i == array[i] } )  
  }
例如:[].equals([])返回true,而[1,2,3].equals( [1,3,2] )返回false

11
通常建议不要修改/扩展现有的全局对象。 - Kunal Kapadia
4
使用 === 比使用 == 更好,对吗? - ChetPrickles
1
@ChetPrickles 取决于你想要什么,一如既往 :-) 你想让 undefined 等于 null、0等吗?还是不想呢? - rplantiko
1
好的。我只是阅读了这个主题,因为我正在寻找相同的东西,但没有索引相等性。但没错,你是对的。 - NoxFly
1
如果数组是同一个对象,您可以在返回值的开头添加 this == array || 以提前退出。 - shieldgenerator7
显示剩余4条评论

20

即使这看起来超级简单,有时它确实非常有用。如果你只需要查看两个数组是否具有相同的项目,并且它们是按相同顺序出现的,请尝试以下操作:

[1, 2, 3].toString() == [1, 2, 3].toString()
true
[1, 2, 3,].toString() == [1, 2, 3].toString()
true
[1,2,3].toString() == [1, 2, 3].toString()
true

然而,这对于更高级的情况并不适用,例如:

[[1,2],[3]].toString() == [[1],[2,3]].toString()
true

这取决于您需要什么。


1
一个有趣的回答!很有用。 - Koerr
[3, 2, 1].toString() == [1, 2, 3].toString() 是什么意思? - Reza
@Reza,那行不通,除非排序。 - Kevin Danikowski
1
@KevinDanikowski 是的,那正是我的意思,我指的情况这个答案不适用。 - Reza

10

根据Tim James的答案和Fox32的评论,以下代码应该检查null值,假设两个null不相等。

function arrays_equal(a,b) { return !!a && !!b && !(a<b || b<a); }

> arrays_equal([1,2,3], [1,3,4])
false
> arrays_equal([1,2,3], [1,2,3])
true
> arrays_equal([1,3,4], [1,2,3])
false
> arrays_equal(null, [1,2,3])
false
> arrays_equal(null, null)
false

4
在JavaScript中,由于null==null(甚至更进一步地说,null===null),因此在数组等价性检查中将两个null视为相等可能更为合适。 - Halil Özgür
5
正如该答案中指出的那样,arrays_equal([1, [2, 3]],[[1, 2], 3])会返回true - Dennis
2
此外,arrays_equal(["1,2"], ["1,2"]) 被视为相等,同样的 arrays_equal([], [""]) 也是如此。 - Mike Samuel

5
在检查数组的大小之后,使用for循环逐个检查每个值。
function equalArray(a, b) {
    if (a.length === b.length) {
        for (var i = 0; i < a.length; i++) {
            if (a[i] !== b[i]) {
                return false;
            }
        }
        return true;
    } else {
        return false;
    }
}

@feihcsim 我认为这个肯定没有误报。 - Default

4
如果您使用lodash且不想修改任何一个数组,可以使用函数_.xor()。它将两个数组作为集合进行比较,并返回包含它们差异的集合。如果这种差异的长度为零,则这两个数组基本上是相等的:
var a = [1, 2, 3];
var b = [3, 2, 1];
var c = new Array(1, 2, 3);
_.xor(a, b).length === 0
true
_.xor(b, c).length === 0
true

4

jQuery有一个名为deepEqual的方法,可以进行深度递归比较。

自制的通用严格相等检查可以如下所示:

function deepEquals(obj1, obj2, parents1, parents2) {
    "use strict";
    var i;
    // compare null and undefined
    if (obj1 === undefined || obj2 === undefined || 
        obj1 === null || obj2 === null) {
        return obj1 === obj2;
    }

    // compare primitives
    if (typeof (obj1) !== 'object' || typeof (obj2) !== 'object') {
        return obj1.valueOf() === obj2.valueOf();
    }

    // if objects are of different types or lengths they can't be equal
    if (obj1.constructor !== obj2.constructor || (obj1.length !== undefined && obj1.length !== obj2.length)) {
        return false;
    }

    // iterate the objects
    for (i in obj1) {
        // build the parents list for object on the left (obj1)
        if (parents1 === undefined) parents1 = [];
        if (obj1.constructor === Object) parents1.push(obj1);
        // build the parents list for object on the right (obj2)
        if (parents2 === undefined) parents2 = [];
        if (obj2.constructor === Object) parents2.push(obj2);
        // walk through object properties
        if (obj1.propertyIsEnumerable(i)) {
            if (obj2.propertyIsEnumerable(i)) {
                // if object at i was met while going down here
                // it's a self reference
                if ((obj1[i].constructor === Object && parents1.indexOf(obj1[i]) >= 0) || (obj2[i].constructor === Object && parents2.indexOf(obj2[i]) >= 0)) {
                    if (obj1[i] !== obj2[i]) {
                        return false;
                    }
                    continue;
                }
                // it's not a self reference so we are here
                if (!deepEquals(obj1[i], obj2[i], parents1, parents2)) {
                    return false;
                }
            } else {
                // obj2[i] does not exist
                return false;
            }
        }
    }
    return true;
};

测试:

// message is displayed on failure
// clean console === all tests passed
function assertTrue(cond, msg) {
    if (!cond) {
        console.log(msg);
    }
}

var a = 'sdf',
    b = 'sdf';
assertTrue(deepEquals(b, a), 'Strings are equal.');
b = 'dfs';
assertTrue(!deepEquals(b, a), 'Strings are not equal.');
a = 9;
b = 9;
assertTrue(deepEquals(b, a), 'Numbers are equal.');
b = 3;
assertTrue(!deepEquals(b, a), 'Numbers are not equal.');
a = false;
b = false;
assertTrue(deepEquals(b, a), 'Booleans are equal.');
b = true;
assertTrue(!deepEquals(b, a), 'Booleans are not equal.');
a = null;
assertTrue(!deepEquals(b, a), 'Boolean is not equal to null.');
a = function () {
    return true;
};
assertTrue(deepEquals(
[
    [1, 1, 1],
    [2, 'asdf', [1, a]],
    [3, {
        'a': 1.0
    },
    true]
], 
[
    [1, 1, 1],
    [2, 'asdf', [1, a]],
    [3, {
        'a': 1.0
    },
    true]
]), 'Arrays are equal.');
assertTrue(!deepEquals(
[
    [1, 1, 1],
    [2, 'asdf', [1, a]],
    [3, {
        'a': 1.0
    },
    true]
],
[
    [1, 1, 1],
    [2, 'asdf', [1, a]],
    [3, {
        'a': '1'
    },
    true]
]), 'Arrays are not equal.');
a = {
    prop: 'val'
};
a.self = a;
b = {
    prop: 'val'
};
b.self = a;
assertTrue(deepEquals(b, a), 'Immediate self referencing objects are equal.');
a.prop = 'shmal';
assertTrue(!deepEquals(b, a), 'Immediate self referencing objects are not equal.');
a = {
    prop: 'val',
    inside: {}
};
a.inside.self = a;
b = {
    prop: 'val',
    inside: {}
};
b.inside.self = a;
assertTrue(deepEquals(b, a), 'Deep self referencing objects are equal.');
b.inside.self = b;
assertTrue(!deepEquals(b, a), 'Deep self referencing objects are not equeal. Not the same instance.');
b.inside.self = {foo: 'bar'};
assertTrue(!deepEquals(b, a), 'Deep self referencing objects are not equal. Completely different object.');
a = {};
b = {};
a.self = a;
b.self = {};
assertTrue(!deepEquals(b, a), 'Empty object and self reference of an empty object.');

你的代码如何处理自我引用的对象? - jusio
@jusio 你好,抱歉回复晚了,最近比较忙。这个特定的代码并没有涉及到自我引用,但你可以看一下它的改进版本。等我再进行一些测试和收集反馈后,我会编辑答案中的代码。- uKolka - uKolka
@jusio 这里是新版本。我已经更新了它以适应新的测试用例。在var a = {},b = {}; a.self = a; b.self = {};和其他一些情况下,它会失败。 - uKolka
请不要编辑您没有所有权的对象的原型。 - Umur Kontacı
@Umur Kontacı 有罪。我会在某个时候更新它。 - uKolka
编辑了答案。更改如下:
  • 考虑自引用对象。
  • 从对象原型中移动。
  • 添加了测试。
- uKolka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接