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;
}
stringify
或< >
?请在您的回答中解释原因。 为了避免XSS攻击,我们不应该使用stringify
或< >
。这些字符可以在Web应用程序中嵌入恶意代码,并使攻击者能够访问用户的敏感数据。相反,我们应该使用安全的HTML编码技术来转义输入并保护应用程序的安全性。 - Jonathan Bennreturn a.every((val, idx) => val === b[idx])
- Mr5o1[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);
}
{1:2,3:4}
的JSON可能等于或不等于{3:4,1:2}
;这取决于实现,规范没有任何保证。[2017更新:实际上,ES6规范现在保证对象键将按照1)整数属性的顺序,2)按照定义的顺序排列的属性,然后是3)按照定义的顺序排列的符号属性的顺序迭代。因此,如果JSON.stringify实现遵循此方式,则相等的对象(在===意义上但不一定是==意义上)将字符串化为相等的值。需要更多研究。因此,我想您可以制作一个具有相反顺序属性的对象的邪恶克隆,但我无法想象它会意外发生...]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
-->
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
==
的相等概念,则需要知道虚假值和强制转换意味着 ==
等式不是传递的。例如,''==0
和0=='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!)。这通常不是大多数同质数据类型集合的问题。
总结递归相等性在集合上的复杂性:
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
将NaN“等同于”将它们视为相同的值时出现为键setKeys.sort((a,b)=> /*some comparison function*/)
,因为在ecmascript中没有全序关系(''==0 and 0=='0',但''!='0'...尽管我相信您可能能够自己定义一个,这肯定是一个崇高的目标)。.toString
或JSON.stringify
所有元素来帮助我们。然后我们将对它们进行排序,这给我们了可能为假阳性的等价类(两个相同的东西不会有相同的字符串JSON表示),(两个不同的东西可能具有相同的字符串或JSON表示)。
Set
树;每个节点将属于O(深度)不同的序列化!new Set([1,2,3])
和 new Set([1,2,3])
它将返回 false。如果您知道自己在做什么,可以通过一些努力重写代码的那部分。.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);
}
allTrue
,您也可以使用返回数组元素值的函数与array.every
一起使用。 - pimvdbJSON.stringify(null) === 'null'
(字符串“null”),而不是null
。 - djechlin对于像数字和字符串这样的原始值,这是一个简单的解决方案:
a = [1,2,3]
b = [3,2,1]
a.sort().toString() == b.sort().toString()
调用 sort()
方法将确保元素的顺序不重要。调用 toString()
方法将创建一个以逗号分隔值的字符串,因此可以测试这两个字符串是否相等。
Array.prototype.sort()
是浅层排序,Array.prototype.toString()
将对象转换为[object Object]
并扁平化任何嵌套的数组,这可能导致假阳性。 - Natea.sort()
不仅仅返回排序后的数组,它会改变原始数组本身,这可能会对应用程序产生意想不到的影响。 - pilat['1,2',3].toString() === [1,'2,3'].toString()
是一个错误的匹配。 - feihcsimjQuery没有比较数组的方法。不过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
false|false
。_.isEqual(a,b)
根据它们的顺序比较数组的元素,因此如果需要进行无序比较,则必须在比较之前对数组进行排序。 - Paul使用 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
。undefined
等于 null
、0等吗?还是不想呢? - rplantikothis == array ||
以提前退出。 - shieldgenerator7即使这看起来超级简单,有时它确实非常有用。如果你只需要查看两个数组是否具有相同的项目,并且它们是按相同顺序出现的,请尝试以下操作:
[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根据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
null==null
(甚至更进一步地说,null===null
),因此在数组等价性检查中将两个null视为相等可能更为合适。 - Halil Özgürarrays_equal(["1,2"], ["1,2"])
被视为相等,同样的 arrays_equal([], [""])
也是如此。 - Mike Samuelfunction 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;
}
}
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
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.');
a.every(item => b.includes(item)) && b.every(item => a.includes(item))
。不要忘记检查双方,因为如果你只执行一个every
,你会处理这种情况:a=[3,3,3], b=[1,2,3]
而导致结果为假。 - NoxFly[1,2,3]
将等于[1,3,2]
并且等于[1,2,3,3]
。 - Richard Wheeldon