【问题标题】:How to check if two arrays are equal with JavaScript? [duplicate]如何检查两个数组是否与 JavaScript 相等? [复制]
【发布时间】:2011-03-08 03:45:16
【问题描述】:
var a = [1, 2, 3];
var b = [3, 2, 1];
var c = new Array(1, 2, 3);

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

demo

如何检查这些数组是否相等并获得一个方法,如果它们相等则返回 true

jQuery 是否为此提供任何方法?

【问题讨论】:

  • 这个例子似乎暗示不应该考虑数字的顺序([1, 2, 3] == [3, 2, 1])。但是,未解决此问题的答案被标记为正确,并且实际解决此问题的唯一答案(使用 sort())被投反对票......如果排序不相关,则应在例子。否则,标记为正确的答案不可能是正确的。
  • 这样对两个Numbers 进行浅层比较不是最简单的方法吗? a.every((v, i) => v === b[i]) b.every((v, i) => v === c[i])
  • 在 ES6 中,只需执行 a.every(item => b.includes(item)) && b.every(item => a.includes(item))。别忘了两边都检查一下,因为如果你只做一个every,你可以处理这种情况:a=[3,3,3], b=[1,2,3]虽然它是假的。

标签: javascript


【解决方案1】:

这是你应该做的。请不要使用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;
}

【讨论】:

  • (这原本不是我的代码,但写它的人不想发布它)
  • 为什么要对代码进行排序?
  • 如果数组包含对象,这不起作用。
  • @GijsjanB 如果数组包含相同的对象,它确实有效。当然,如果对象不相同,并且您想比较对象本身,则它不起作用。但这不是问题。
  • 您能否在回答中解释一下为什么我们不应该使用stringify&lt; &gt;
【解决方案2】:

[2021 更新日志:选项 4 的错误修正:js 对象上的 no total ordering(甚至不包括 NaN!=NaN'5'==5'5'===5'2'&lt;3 等)),因此不能在地图上使用 .sort(cmpFunc)。 keys() (尽管你可以在 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 相等的概念。如果使用内置的 Set 相等概念(即===),那么上面的方法应该可以工作。然而,如果一个人使用像== 这样的非传递性平等概念,你就会打开一堆蠕虫:即使你强迫用户在域上定义一个散列函数(hash(a)!=hash(b) 意味着!=b) 我不确定这会有所帮助...当然可以做 O(N^2) 性能的事情并像冒泡排序一样逐个删除 == 项目对,然后再做第二个 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 同义,隐式使用 ===-equality ([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);这是equivalence class 的定义
  • 等式 == 运算符显然不符合其中的许多属性
  • 即使是 ecmascript 中的严格相等 === operator 不服从这些属性,因为 strict equality comparison algorithm of ecmascript 有 NaN!=NaN;这就是为什么许多原生数据类型(如 SetMap 'equate' NaNs)在它们作为键出现时将它们视为相同的值
  • 只要我们强制并确保递归集合相等确实是可传递的、自反的和对称的,我们就可以确保不会发生任何可怕的错误。
    • 然后,我们可以通过递归地随机比较所有内容来进行 O(N^2) 比较,这是非常低效的。没有神奇的算法可以让我们执行setKeys.sort((a,b)=&gt; /*some comparison function*/),因为 ecmascript 中没有总排序(''==0 和 0=='0',但是 ''!='0'... 虽然我相信你可能能够自己定义一个,这肯定是一个崇高的目标)。
    • 但是,我们可以.toStringify 或JSON.stringify 所有元素来帮助我们。然后我们将对它们进行排序,这为我们提供了潜在误报(两个不同的事物可能具有相同的字符串或 JSON 表示)的等价类(两个相同的事物不会具有相同的字符串 JSON 表示)。
      • 但是,这会引入其自身的性能问题,因为序列化相同的事物,然后一遍又一遍地序列化该事物的子集,效率非常低。想象一棵嵌套Sets 的树;每个节点都属于 O(depth) 不同的序列化!
      • 即使这不是问题,如果所有序列化“提示”都相同,最坏情况下的性能仍然是 O(N!)

因此,上面的实现声明如果项目只是简单的 ===(而不是递归 ===),则 Set 是相等的。这意味着它将为 new Set([1,2,3])new Set([1,2,3]) 返回 false。如果您知道自己在做什么,则可以通过一些努力重写那部分代码。

(旁注:地图是 es6 字典。我不知道它们是否具有 O(1) 或 O(log(N)) 查找性能,但无论如何它们是“有序”的,因为它们可以跟踪键值对插入其中的顺序。但是,如果元素以不同的顺序插入其中,两个 Maps 是否应该相等的语义是模棱两可的。我在下面给出了一个 deepEquals 的示例实现,它考虑了两个即使元素以不同的顺序插入其中,映射也是相等的。)

(注 [1]:重要提示:平等概念:您可能希望用自定义平等概念覆盖所指出的行,您还必须在出现的任何其他函数中更改它。对于例如,你是否想要 NaN==NaN?默认情况下不是这样。还有更奇怪的东西,比如 0=='0'。你认为两个对象是相同的当且仅当它们是内存中的同一个对象?请参阅https://stackoverflow.com/a/5447170/711085。您应该记录您使用的平等概念。)另请注意,其他天真使用.toString.sort 的答案有时可能会祈祷0!=-0但对于几乎所有数据类型和 JSON 序列化,都被认为是相等且可规范化为 0; whether -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);
}

【讨论】:

  • +1,一点注释:对于allTrue,您也可以将array.every与返回数组元素值的函数一起使用。
  • JSON.stringify(null) === 'null'(字符串“null”),而不是null
  • undefined 未转换为字符串'null'...您能花点时间了解一下 JSON.stringify 的工作原理吗?
  • 哎呀,好像回到那天我很匆忙并欺骗自己误读了某人的评论,所以说了一个不相关的谎言(那个 JSON.stringify(null)=='"null"' )。对于我在回答中没有提到的任何未定义或空值,我仍然没有看到与 OP 的问题有关的问题。因此,我坚持我的答案及其演示测试。
  • JSON.stringify 不保证它将对象转换为相同的字符串。每次。
【解决方案3】:

jQuery 没有比较数组的方法。然而Underscore library(或类似的 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 的一个很好的补充。

编辑:正如在 cmets 中所指出的那样,以上内容现在仅在两个数组的元素顺序相同时才有效,即:

_.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

【讨论】:

  • Underscorejs 是我日常使用的库。它的简单性绝对应该得到更高的评价。
  • 下划线已被更高级的库 lodash 取代。由于它是下划线的超集,它还支持 _.isEqual(a, b)。查看this了解更多详情。
  • 我喜欢 lodash,它确实包含了 Underscore 的超集特性……但它不是“优越”,也没有“取代”Underscore。一方面,Lodash 已经如此元编程和微优化,以至于它的源代码基本上是不可读的。当(比如说)你不小心将错误的参数传递给 Lodash/Underscore 函数并且必须调试正在发生的事情时,这很重要。在这种情况下,Underscore 非常 更优越,因为(与 Lodash 不同)您实际上可以阅读源代码。归根结底,这两个库都不优于另一个,它们只是有不同的优势和劣势。
  • 作为旁注,这两个库的作者(John-David Dalton 和 Jeremy Ashkenas)最近讨论了合并它们的前景,因为它们有共同点。但是(如果您阅读 GitHub 线程,您会发现:github.com/jashkenas/underscore/issues/2182)这不是一个显而易见的决定。事实上,AFAIK 在那之后的 3 个多月里都没有做出任何决定,正是因为 Underscore 具有诸如 Underscore 用户不想失去的可读源代码等优势。
  • 回到手头的话题,这会提醒false|false_.isEqual(a,b) 根据数组元素的顺序比较元素,因此如果需要不区分顺序的比较,则必须在比较之前对数组进行排序。
【解决方案4】:

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

a = [1,2,3]

b = [3,2,1]

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

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

【讨论】:

  • 如果您的数组包含简单值以外的任何内容,请小心。 Array.prototype.sort() 很浅,Array.prototype.toString() 将对象转换为 [object Object] 并展平任何嵌入的数组,这可能会导致误报。
  • 我不明白为什么这被否决:这是迄今为止唯一真正使(未指定的)示例正确的解决方案......
  • a.sort() 不仅返回排序后的版本。它改变了数组本身,可能会以意想不到的方式影响一个人的应用程序。
  • @feihcsim 你错了。 [12, 34, 56].toString() //results: "12,34,56" while [1, 23, 456].toString() // results: "1,23,456" 因此,它们不相等
  • 啊,你是对的 - 但问题仍然存在:['1,2',3].toString() === [1,'2,3'].toString() 是误报
【解决方案5】:

使用 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

【讨论】:

  • 一般建议不要修改/扩展已有的全局对象。
  • 最好使用 === 而不是 == 对吗?
  • @ChetPrickles 取决于你想要什么,像往常一样 :-) 你是否希望 undefined 等于 null、0 等等。
  • 如果您有 2 个类似这样的数组,则它不起作用:[3,3,3] and [1,2,3],因为您的代码将返回 true 而不是。所以你必须在两边都做every
  • 好的。我刚刚讨论了这个话题,因为我正在寻找相同的东西,但没有索引相等。但好吧,你是对的。
【解决方案6】:

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

[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

这取决于你需要什么。

【讨论】:

  • 一个有趣的答案!很有用
  • [3, 2, 1].toString() == [1, 2, 3].toString()怎么样
  • @Reza 那行不通,除非排序,否则它们是不同的
  • @KevinDanikowski 是的,这正是我的意思,我的意思是这个答案不适用于我所指的情况
【解决方案7】:

根据 Tim James answer 和 Fox32 的评论,以下应检查空值,假设两个空值不相等。

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

【讨论】:

  • 既然 Javascript 中的 null==null(甚至更多,null===null),在数组等价检查中也将两个空值视为相等不是更合适吗?
  • 正如this answer 中所指出的,arrays_equal([1, [2, 3]],[[1, 2], 3]) 将返回true
  • 另外,arrays_equal(["1,2"], ["1,2"]) 被视为平等,arrays_equal([], [""]) 也是如此。
【解决方案8】:

检查数组大小后,通过 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 我认为这个肯定没有误报
【解决方案9】:

jQuery 有这样的方法for deep recursive comparison.

一个本土通用的严格相等检查可能如下所示:

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 嗨,抱歉耽搁了,没有太多空闲时间。这个特别的不处理自我引用,但看看它的improved version。经过更多的测试和反馈后,我将编辑答案中的那个。 – uKolka
  • @jusio 这是newer version。我已经为新的测试用例更新了它。在 var a = {}, b = {}; a.self = a; b.self = {}; 和其他一些情况下,它失败了。
  • 请不要编辑不属于您的对象的原型
  • @Umur Kontacı 有罪。有时间我会更新的。
【解决方案10】:

如果你正在使用 lodash 并且不想修改任何一个数组,你可以使用the function _.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

【讨论】:

    【解决方案11】:

    使用map()reduce()

    function arraysEqual (a1, a2) {
        return a1 === a2 || (
            a1 !== null && a2 !== null &&
            a1.length === a2.length &&
            a1
                .map(function (val, idx) { return val === a2[idx]; })
                .reduce(function (prev, cur) { return prev && cur; }, true)
        );
    }
    

    【讨论】:

    • 这不是问题的相关答案。我在 jsbin 中试过这个,但它不适用于有问题的输入。
    • 由于问题不是 100% 清楚地表明这个算法的顺序是可以的。但它的性能很差,因为它在数组上完全迭代了两次。对于较大的数组,这可能是个问题。
    【解决方案12】:

    如果你想检查对象数组是否相等并且顺序无关紧要,即

    areEqual([{id: "0"}, {id: "1"}], [{id: "1"}, {id: "0"}]) // true

    您需要先对数组进行排序。 lodash 拥有您需要的所有工具,通过组合 sortByisEqual

    // arr1 & arr2: Arrays of objects 
    // sortProperty: the property of the object with which you want to sort
    // Note: ensure every object in both arrays has your chosen sortProperty
    // For example, arr1 = [{id: "v-test_id0"}, {id: "v-test_id1"}]
    // and          arr2 = [{id: "v-test_id1"}, {id: "v-test_id0"}]
    // sortProperty should be 'id'
    
    function areEqual (arr1, arr2, sortProperty) {
      return _.areEqual(_.sortBy(arr1, sortProperty), _.sortBy(arr2, sortProperty))
    }
    

    编辑:由于sortBy 返回一个新数组,因此无需在排序之前克隆您的数组。原始数组不会发生变异。

    请注意,对于 lodash 的 isEqual顺序很重要。如果sortBy 没有首先应用于每个数组,上面的示例将返回false

    【讨论】:

      【解决方案13】:

      这个方法很烂,但我把它留在这里供参考,所以其他人避开这条路:


      使用@ninjagecko 的选项 1 最适合我:

      Array.prototype.equals = function(array) {
          return array instanceof Array && JSON.stringify(this) === JSON.stringify(array) ;
      }
      
      a = [1, [2, 3]]
      a.equals([[1, 2], 3]) // false
      a.equals([1, [2, 3]]) // true
      

      它还将处理 null 和未定义的情况,因为我们将它添加到数组的原型中并检查另一个参数是否也是一个数组。

      【讨论】:

      • 问题,如果数组中包含一个对象,该对象的属性指向同一个对象,会发生什么?
      • TypeError: Converting circular structure to JSON
      • 感谢@jusio 指出这一点,我已在答案中明确说明。
      【解决方案14】:

      没有简单的方法可以做到这一点。我也需要这个,但想要一个可以接受任何两个变量并测试相等性的函数。这包括非对象值、对象、数组和任何级别的嵌套。

      在您的问题中,您提到要忽略数组中值的顺序。我的解决方案本身并没有这样做,但是您可以通过在比较相等性之前对数组进行排序来实现它

      我还想要将非对象转换为字符串的选项,以便 [1,2]===["1",2]

      由于我的项目使用了 UnderscoreJs,我决定将其作为一个 mixin 而不是一个独立的函数。

      你可以在http://jsfiddle.net/nemesarial/T44W4/上测试一下

      这是我的mxin:

      _.mixin({
        /**
        Tests for the equality of two variables
          valA: first variable
          valB: second variable
          stringifyStatics: cast non-objects to string so that "1"===1
        **/
        equal:function(valA,valB,stringifyStatics){
          stringifyStatics=!!stringifyStatics;
      
          //check for same type
          if(typeof(valA)!==typeof(valB)){
            if((_.isObject(valA) || _.isObject(valB))){
              return false;
            }
          }
      
          //test non-objects for equality
          if(!_.isObject(valA)){
            if(stringifyStatics){
              var valAs=''+valA;
              var valBs=''+valB;
              ret=(''+valA)===(''+valB);
            }else{
              ret=valA===valB;
            }
            return ret;
          }
      
          //test for length
          if(_.size(valA)!=_.size(valB)){
            return false;
          }
      
          //test for arrays first
          var isArr=_.isArray(valA);
      
          //test whether both are array or both object
          if(isArr!==_.isArray(valB)){
            return false;
          }
      
          var ret=true;
          if(isArr){
            //do test for arrays
            _.each(valA,function(val,idx,lst){
              if(!ret){return;}
              ret=ret && _.equal(val,valB[idx],stringifyStatics);
            });
          }else{
            //do test for objects
            _.each(valA,function(val,idx,lst){
              if(!ret){return;}
      
              //test for object member exists
              if(!_.has(valB,idx)){
                ret=false;
                return;
              }
      
              // test for member equality
              ret=ret && _.equal(val,valB[idx],stringifyStatics);
            });
      
          }
          return ret;
        }
      });
      

      这是你使用它的方式:

      _.equal([1,2,3],[1,2,"3"],true)
      

      要演示嵌套,您可以这样做:

      _.equal(
          ['a',{b:'b',c:[{'someId':1},2]},[1,2,3]],
          ['a',{b:'b',c:[{'someId':"1"},2]},["1",'2',3]]
      ,true);
      

      【讨论】:

        【解决方案15】:

        它处理所有可能的东西,甚至在对象结构中引用自己。您可以在代码末尾看到示例。

        var deepCompare = (function() {
            function internalDeepCompare (obj1, obj2, objects) {
                var i, objPair;
        
                if (obj1 === obj2) {
                    return true;
                }
        
                i = objects.length;
                while (i--) {
                    objPair = objects[i];
                    if (  (objPair.obj1 === obj1 && objPair.obj2 === obj2) ||
                          (objPair.obj1 === obj2 && objPair.obj2 === obj1)  ) {                          
                        return true;
                    }                    
                }
                objects.push({obj1: obj1, obj2: obj2});
        
                if (obj1 instanceof Array) {
                    if (!(obj2 instanceof Array)) {
                        return false;
                    }
        
                    i = obj1.length;
        
                    if (i !== obj2.length) {
                       return false; 
                    }
        
                    while (i--) {
                        if (!internalDeepCompare(obj1[i], obj2[i], objects)) {
                            return false;
                        }
                    }
                }
                else {
                    switch (typeof obj1) {
                        case "object":                
                            // deal with null
                            if (!(obj2 && obj1.constructor === obj2.constructor)) {
                                return false;
                            }
        
                            if (obj1 instanceof RegExp) {
                                if (!(obj2 instanceof RegExp && obj1.source === obj2.source)) {
                                    return false;
                                }
                            }                 
                            else if (obj1 instanceof Date) {
                                if (!(obj2 instanceof Date && obj1.getTime() === obj2.getTime())) {
                                    return false;
                                }
                            } 
                            else {    
                                for (i in obj1) {
                                    if (obj1.hasOwnProperty(i)) {       
                                        if (!(obj2.hasOwnProperty(i) && internalDeepCompare(obj1[i], obj2[i], objects))) {
                                            return false;
                                        }
                                    }
                                }         
                            }
                            break;
                        case "function": 
                            if (!(typeof obj2 === "function" && obj1+"" === obj2+"")) {
                                return false;
                            }
                            break;
                        default:                 //deal with NaN 
                            if (obj1 !== obj2 && obj1 === obj1 && obj2 === obj2) {
                                return false;            
                            }
                    }
                }
        
                return true;
            }
        
            return function (obj1, obj2) {
                return internalDeepCompare(obj1, obj2, []);    
            };
        }());
        
        /*    
        var a = [a, undefined, new Date(10), /.+/, {a:2}, function(){}, Infinity, -Infinity, NaN, 0, -0, 1, [4,5], "1", "-1", "a", null],
            b = [b, undefined, new Date(10), /.+/, {a:2}, function(){}, Infinity, -Infinity, NaN, 0, -0, 1, [4,5], "1", "-1", "a", null];
        deepCompare(a, b);
        */
        

        【讨论】:

          【解决方案16】:
          var a= [1, 2, 3, '3'];
          var b = [1, 2, 3];
          
          var c = a.filter(function (i) { return ! ~b.indexOf(i); });
          
          alert(c.length);
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-02-12
            • 2017-09-09
            • 2019-07-27
            • 2017-02-20
            相关资源
            最近更新 更多