如何检查两个数组是否与JavaScript相等?
var a = [1, 2, 3];
var b = [3, 2, 1];
var c = new Array(1, 2, 3);
alert(a == b + "|" + b == c);
我如何检查这些数组的相等性,并获得一个如果它们相等则返回的方法?true
jQuery是否为此提供了任何方法?
var a = [1, 2, 3];
var b = [3, 2, 1];
var c = new Array(1, 2, 3);
alert(a == b + "|" + b == c);
我如何检查这些数组的相等性,并获得一个如果它们相等则返回的方法?true
jQuery是否为此提供了任何方法?
这是你应该做的。请不要使用或 .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;
}
[2021 changelog:选项4的错误修复:对js对象没有总排序(甚至不包括和(,等)),因此不能在Map.keys()上使用(尽管你可以在,因为即使是“数字”键也是字符串)。NaN!=NaN
'5'==5
'5'===5
'2'<3
.sort(cmpFunc)
Object.keys(obj)
备选案文1
最简单的选项,几乎适用于所有情况,除了!==,但它们都被转换为JSON表示形式并被视为相等:null
undefined
null
function arraysEqual(a1,a2) {
/* WARNING: arrays must not contain {objects} or behavior may be undefined */
return JSON.stringify(a1)==JSON.stringify(a2);
}
(如果您的数组包含对象,这可能不起作用。这是否仍适用于对象取决于 JSON 实现是否对键进行排序。例如,JSON 可能等于也可能不等于 ;这取决于实现,规范不作任何保证。[2017年更新:实际上,ES6规范现在保证对象键将按1)整数属性,2)属性的定义顺序迭代,然后3)符号属性按定义顺序迭代。因此,如果 JSON.stringify 实现遵循此值,则相等的对象(在 === 意义上但不一定在 == 意义上)将字符串化为相等值。需要更多的研究。所以我想你可以用相反的顺序制作一个具有相反属性的物体的邪恶克隆,但我无法想象它会偶然发生......]至少在Chrome上,JSON.stringify函数倾向于按照定义的顺序返回键(至少我注意到这一点),但这种行为在任何时候都会发生变化,不应该被依赖。如果您选择不使用列表中的对象,这应该可以正常工作。如果列表中确实有对象都具有唯一的 ID,则可以执行 。如果列表中有任意对象,则可以继续阅读选项 #2。{1:2,3:4}
{3:4,1:2}
a1.map(function(x)}{return {id:x.uniqueId}})
这也适用于嵌套数组。
但是,由于创建这些字符串并对其进行垃圾收集的开销,因此效率略低。
备选案文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
请注意,如果一个人使用相等的概念,那么要知道假值和强制意味着相等不是可传递的。例如,和 但是 。这与集合有关:我不认为人们可以以有意义的方式覆盖集合相等的概念。如果使用集合相等的内在概念(即 ),则上述内容应该有效。然而,如果一个人使用一个非传递的平等概念,比如,你打开一罐蠕虫:即使你强迫用户在域上定义一个哈希函数(hash(a)!=hash(b)意味着a!=b)我不确定这是否会有所帮助......当然,人们可以做O(N^2)性能的事情,像气泡排序一样逐个删除成对的项目,然后做第二个O(N^2)传递来确认等价类中的东西实际上是相互的,以及所有没有这样配对的东西,但是如果你有一些胁迫,你仍然需要抛出一个运行时错误......你也可能得到奇怪的(但可能不是那么奇怪)的边缘情况,https://developer.mozilla.org/en-US/docs/Glossary/Falsy 和Truthy值(除了NaN==NaN...但只是为了集!对于大多数同构数据类型集,这通常不是问题。==
==
''==0
0=='0'
''!='0'
===
==
==
==
!=
总结集合上递归相等的复杂性:
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]])
Set
Map
setKeys.sort((a,b)=> /*some comparison function*/)
.toString
JSON.stringify
Set
因此,上述实现声明,如果项目只是普通的 ===(不是递归 ===),则集合是相等的。这意味着 它将返回 false 和 。只需付出一些努力,如果你知道自己在做什么,就可以重写这部分代码。new Set([1,2,3])
new Set([1,2,3])
(附注:地图是es6字典。我无法判断它们是否具有O(1)或O(log(N))查找性能,但无论如何,它们都是“有序的”,因为它们跟踪键值对插入其中的顺序。但是,如果元素以不同的顺序插入到它们中,则两个 Map 是否应相等的语义是模棱两可的。我在下面给出了一个deepEquals的示例实现,即使元素以不同的顺序插入到它们中,它也认为两个映射相等。
(注意 [1]:重要提示:相等性的概念:您可能希望使用自定义相等概念覆盖所记录的行,并且还必须在其他函数中更改它。例如,您是否想要 NaN==NaN?默认情况下,情况并非如此。还有更多奇怪的事情,比如0=='0'。你认为两个对象是相同的,当且仅当它们在内存中是同一个对象时,它们会是相同的吗?请参阅 https://stackoverflow.com/a/5447170/711085 。你应该记录你使用的平等的概念。还要注意,其他天真地使用.toString
和.sort
的答案有时可能会下降到0!=-0
的事实,但对于几乎所有数据类型和JSON序列化,它们被认为是相等的,并且可以规范化为0;-0==0 是否也应该记录在你的平等概念中,以及该表中的大多数其他事物,如 NaN 等。
您应该能够将上述内容扩展到 WeakMaps,WeakSets。不确定扩展到 DataViews 是否有意义。也应该能够扩展到正则表达式,等等。
当你扩展它时,你会意识到你做了很多不必要的比较。这就是我之前定义的函数(解决方案#2)可以派上用场的地方;然后您可以立即发货。这是否值得(可能?不确定它在引擎盖下是如何工作的)表示类型的字符串的开销取决于您。然后,您可以将调度程序(即函数)重写为:type
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);
}