How to test if two objects are the same with JavaScript? - javascript

I need a function:
function isSame(a, b){
}
In which, if a and b are the same, it returns true.
, I tried return a === b, but I found that [] === [] will return false.
Some results that I expect this function can gave:
isSame(3.14, 3.14); // true
isSame("hello", "hello"); // true
isSame([], []); // true
isSame([1, 2], [1, 2]); // true
isSame({ a : 1, b : 2}, {a : 1, b : 2}); //true
isSame([1, {a:1}], [1, {a:1}]); //true

You could embed Underscore.js and use _.isEqual(obj1, obj2).
The function works for arbitrary objects and uses whatever is the most efficient way to test the given objects for equality.

the best way to do that is to use a JSON serializer. serialize both to string and compare the string.

There are some examples, adapted from scheme, on Crockford's site. Specifically, check out:
function isEqual(s1, s2) {
return isAtom(s1) && isAtom(s2) ? isEqan(s1, s2) :
isAtom(s1) || isAtom(s2) ? false :
isEqlist(s1, s2);
}
It can all be found here:
http://javascript.crockford.com/little.js
Here is a working example:
http://jsfiddle.net/FhGpd/
Update:
Just wrote some test cases based on the OP. Turns out I needed to modify the sub1 function to check <= 0 not === 0 otherwise isEqual(3.14, 3.14) blew the stack. Also, isEqual does not work for object comparison, so you are on your own there. However, if you follow the examples on Crockford's site you will see how easy and fun it is to write recursive methods that could be used to check for object equality.

Here is something that can work:
function isSame(obj1, obj2, prefer){
// Optional parameter prefer allows to only check for object keys and not both keys and values of an object
var obj_prefer = prefer || "both";
function checkArray(arr1, arr2){
for(var i = 0, j = obj1.length; i<j; i++){
if(obj1[i] !== obj2[i]){return false;}
}
return true;
}
function checkValues(obj_1, obj_2){
for(var prop in obj_1){
if(typeof obj_1[prop] === "function"){ // converting functions to string so that they can be matched
obj_1[prop] = String(obj_1[prop]);
obj_2[prop] = String(obj_2[prop]);
}
if(obj_1[prop] !== obj_2[prop]){ return false;}
}
return true;
}
// The built in === will check everything except when typeof object is "object"
if ( typeof obj1 === "object"){
// typeof Array is object so this is an alternative
if((typeof obj1.push === "function") && (!obj1.hasOwnProperty('push'))){
return checkArray(obj1, obj2);
}
else{
if( obj_prefer !== "keys"){ // do not check for values if obj_prefer is "keys"
return checkValues(obj1, obj2);
}
var keys_1 = Object.keys(obj1);
var keys_2 = Object.keys(obj2);
if(!checkArray(keys_1, keys_2)){return false;}
return true;
}
}
// I thought undefined === undefined will give false but it isn't so you can remove it
if( typeof obj1 === "undefined" && typeof obj2 === "undefined" ){return true}
if(typeof obj1 === "function"){
return String(obj1) === String(obj2);
}
return obj1 === obj2;
}
console.log(isSame(2, 2)); //true
console.log(isSame([1], [1])); // true
Since it converts Functions into Strings to compare them, check out for spaces as that can break things:
var func1 = function(){},
func2 = function(){ }; // function with extra space
isSame(func1, func2); // false
You can check out http://jsfiddle.net/webholik/dwaLN/4/ to try it yourself.

If anyone reading this answer is using Angular.js, you can use angular.equals(obj1,obj2);
According to the docs:
Determines if two objects or two values are equivalent. Supports value
types, regular expressions, arrays and objects.
Two objects or values are considered equivalent if at least one of the
following is true:
Both objects or values pass === comparison.
Both objects or values are of the same type and all of their properties are equal by comparing them with angular.equals.
Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal).
Both values represent the same regular expression (In JavaScript, /abc/ == /abc/ => false. But we consider two regular expressions as
equal when their textual representation matches).
During a property comparison, properties of function type and properties with names that begin with $ are ignored.
Scope and DOMWindow objects are being compared only by identify (===).

Related

Parsing JSON value fields is array or not. ( Javascript) [duplicate]

This question already has answers here:
How do I check if a variable is an array in JavaScript?
(24 answers)
Closed 1 year ago.
I'm trying to write a function that either accepts a list of strings, or a single string. If it's a string, then I want to convert it to an array with just the one item so I can loop over it without fear of an error.
So how do I check if the variable is an array?
The method given in the ECMAScript standard to find the class of Object is to use the toString method from Object.prototype.
if(Object.prototype.toString.call(someVar) === '[object Array]') {
alert('Array!');
}
Or you could use typeof to test if it is a string:
if(typeof someVar === 'string') {
someVar = [someVar];
}
Or if you're not concerned about performance, you could just do a concat to a new empty Array.
someVar = [].concat(someVar);
There's also the constructor which you can query directly:
if (somevar.constructor.name == "Array") {
// do something
}
Check out a thorough treatment from T.J. Crowder's blog, as posted in his comment below.
Check out this benchmark to get an idea which method performs better: http://jsben.ch/#/QgYAV
From #Bharath, convert a string to an array using ES6 for the question asked:
const convertStringToArray = (object) => {
return (typeof object === 'string') ? Array(object) : object
}
Suppose:
let m = 'bla'
let n = ['bla','Meow']
let y = convertStringToArray(m)
let z = convertStringToArray(n)
console.log('check y: '+JSON.stringify(y)) . // check y: ['bla']
console.log('check y: '+JSON.stringify(z)) . // check y: ['bla','Meow']
In modern browsers you can do:
Array.isArray(obj)
(Supported by Chrome 5, Firefox 4.0, Internet Explorer 9, Opera 10.5 and Safari 5)
For backward compatibility you can add the following:
// Only implement if no native implementation is available
if (typeof Array.isArray === 'undefined') {
Array.isArray = function(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
}
};
If you use jQuery you can use jQuery.isArray(obj) or $.isArray(obj). If you use Underscore.js you can use _.isArray(obj).
If you don't need to detect arrays created in different frames you can also just use instanceof:
obj instanceof Array
I would first check if your implementation supports isArray:
if (Array.isArray)
return Array.isArray(v);
You could also try using the instanceof operator
v instanceof Array
jQuery also offers an $.isArray() method:
var a = ["A", "AA", "AAA"];
if($.isArray(a)) {
alert("a is an array!");
} else {
alert("a is not an array!");
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
This is the fastest among all methods (all browsers supported):
function isArray(obj){
return !!obj && obj.constructor === Array;
}
Imagine you have this array below:
var arr = [1,2,3,4,5];
JavaScript (new and older browsers):
function isArray(arr) {
return arr.constructor.toString().indexOf("Array") > -1;
}
or
function isArray(arr) {
return arr instanceof Array;
}
or
function isArray(arr) {
return Object.prototype.toString.call(arr) === '[object Array]';
}
Then call it like this:
isArray(arr);
JavaScript (Internet Explorer 9+, Chrome 5+, Firefox 4+, Safari 5+, and Opera 10.5+)
Array.isArray(arr);
jQuery:
$.isArray(arr);
Angular:
angular.isArray(arr);
Underscore.js and Lodash:
_.isArray(arr);
Array.isArray works fast, but it isn't supported by all versions of browsers.
So you could make an exception for others and use a universal method:
Utils = {};
Utils.isArray = ('isArray' in Array) ?
Array.isArray :
function (value) {
return Object.prototype.toString.call(value) === '[object Array]';
}
A simple function to check this:
function isArray(object)
{
return object.constructor === Array;
}
You can use Array.isArray(). Here is a polyfill:
if (Array.isArray == null) {
Array.isArray = (arr) => Object.prototype.toString.call(arr) === "[object Array]"
}
As MDN says in here:
use Array.isArray or Object.prototype.toString.call to differentiate
regular objects from arrays
Like this:
Object.prototype.toString.call(arr) === '[object Array]', or
Array.isArray(arr)
There's just one line solution for this question
x instanceof Array
where x is the variable it will return true if x is an array and false if it is not.
I would make a function to test the type of object you are dealing with...
function whatAmI(me){ return Object.prototype.toString.call(me).split(/\W/)[2]; }
// tests
console.log(
whatAmI(["aiming","#"]),
whatAmI({living:4,breathing:4}),
whatAmI(function(ing){ return ing+" to the global window" }),
whatAmI("going to do with you?")
);
// output: Array Object Function String
then you can write a simple if statement...
if(whatAmI(myVar) === "Array"){
// do array stuff
} else { // could also check `if(whatAmI(myVar) === "String")` here to be sure
// do string stuff
}
You can check the type of your variable whether it is an array with;
var myArray=[];
if(myArray instanceof Array)
{
....
}
I do this in a very simple way. It works for me.
Array.prototype.isArray = true;
a=[]; b={};
a.isArray // true
b.isArray // (undefined -> false)
This is my attempt to improve on this answer taking into account the comments:
var isArray = myArray && myArray.constructor === Array;
It gets rid of the if/else, and accounts for the possibility of the array being null or undefined
I have updated the jsperf fiddle with two alternative methods as well as error checking.
It turns out that the method defining a constant value in the 'Object' and 'Array' prototypes is faster than any of the other methods. It is a somewhat surprising result.
/* Initialisation */
Object.prototype.isArray = function() {
return false;
};
Array.prototype.isArray = function() {
return true;
};
Object.prototype._isArray = false;
Array.prototype._isArray = true;
var arr = ["1", "2"];
var noarr = "1";
/* Method 1 (function) */
if (arr.isArray()) document.write("arr is an array according to function<br/>");
if (!noarr.isArray()) document.write("noarr is not an array according to function<br/>");
/* Method 2 (value) - **** FASTEST ***** */
if (arr._isArray) document.write("arr is an array according to member value<br/>");
if (!noarr._isArray) document.write("noarr is not an array according to member value<br/>");
These two methods do not work if the variable takes the undefined value, but they do work if you are certain that they have a value. With regards to checking with performance in mind if a value is an array or a single value, the second method looks like a valid fast method. It is slightly faster than 'instanceof' on Chrome, twice as fast as the second best method in Internet Explorer, Opera and Safari (on my machine).
I know, that people are looking for some kind of raw JavaScript approach. But if you want think less about it, take a look at Underscore.js' isArray:
_.isArray(object)
It returns true if object is an Array.
(function(){ return _.isArray(arguments); })();
=> false
_.isArray([1,2,3]);
=> true
The best practice is to compare it using constructor, something like this
if(some_variable.constructor === Array){
// do something
}
You can use other methods too, like typeOf, converting it to a string and then comparing, but comparing it with dataType is always a better approach.
The best solution I've seen is a cross-browser replacement for typeof. Check Angus Croll's solution.
The TL;DR version is below, but the article is a great discussion of the issue so you should read it if you have time.
Object.toType = function(obj) {
return ({}).toString.call(obj).match(/\s([a-z|A-Z]+)/)[1].toLowerCase();
}
// ... and usage:
Object.toType([1,2,3]); //"array" (all browsers)
// or to test...
var shouldBeAnArray = [1,2,3];
if(Object.toType(shouldBeAnArray) === 'array'){/* do stuff */};
If the only two kinds of values that could be passed to this function are a string or an array of strings, keep it simple and use a typeof check for the string possibility:
function someFunc(arg) {
var arr = (typeof arg == "string") ? [arg] : arg;
}
Here's my lazy approach:
if (Array.prototype.array_ === undefined) {
Array.prototype.array_ = true;
}
// ...
var test = [],
wat = {};
console.log(test.array_ === true); // true
console.log(wat.array_ === true); // false
I know it's sacrilege to "mess with" the prototype, but it appears to perform significantly better than the recommended toString method.
Note: A pitfall of this approach is that it wont work across iframe boundaries, but for my use case this is not an issue.
This function will turn almost anything into an array:
function arr(x) {
if(x === null || x === undefined) {
return [];
}
if(Array.isArray(x)) {
return x;
}
if(isString(x) || isNumber(x)) {
return [x];
}
if(x[Symbol.iterator] !== undefined || x.length !== undefined) {
return Array.from(x);
}
return [x];
}
function isString(x) {
return Object.prototype.toString.call(x) === "[object String]"
}
function isNumber(x) {
return Object.prototype.toString.call(x) === "[object Number]"
}
It uses some newer browser features so you may want to polyfill this for maximum support.
Examples:
> arr(null);
[]
> arr(undefined)
[]
> arr(3.14)
[ 3.14 ]
> arr(1/0)
[ Infinity ]
> gen = function*() { yield 1; yield 2; yield 3; }
[Function: gen]
> arr(gen())
[ 1, 2, 3 ]
> arr([4,5,6])
[ 4, 5, 6 ]
> arr("foo")
[ 'foo' ]
N.B. strings will be converted into an array with a single element instead of an array of chars. Delete the isString check if you would prefer it the other way around.
I've used Array.isArray here because it's the most robust and also simplest.
The following could be used if you know that your object doesn't have a concat method.
var arr = [];
if (typeof arr.concat === 'function') {
console.log("It's an array");
}
var a = [], b = {};
console.log(a.constructor.name == "Array");
console.log(b.constructor.name == "Object");
There is a nice example in Stoyan Stefanov's book JavaScript Patterns which is supposed to handle all possible problems as well as use the ECMAScript 5 method Array.isArray().
So here it is:
if (typeof Array.isArray === "undefined") {
Array.isArray = function (arg) {
return Object.prototype.toString.call(arg) === "[object Array]";
};
}
By the way, if you are using jQuery, you can use its method $.isArray().
You could use the isArray method, but I would prefer to check with:
Object.getPrototypeOf(yourvariable) === Array.prototype
function isArray(value) {
if (value) {
if (typeof value === 'object') {
return (Object.prototype.toString.call(value) == '[object Array]')
}
}
return false;
}
var ar = ["ff","tt"]
alert(isArray(ar))
A simple function for testing if an input value is an array is the following:
function isArray(value)
{
return Object.prototype.toString.call(value) === '[object Array]';
}
This works cross browser, and with older browsers. This is pulled from T.J. Crowders' blog post
You can try this:
var arr = []; (or) arr = new Array();
var obj = {}; (or) arr = new Object();
arr.constructor.prototype.hasOwnProperty('push') //true
obj.constructor.prototype.hasOwnProperty('push') // false
In your case you may use concat method of Array which can accept single objects as well as array (and even combined):
function myFunc(stringOrArray)
{
var arr = [].concat(stringOrArray);
console.log(arr);
arr.forEach(function(item, i)
{
console.log(i, "=", item);
})
}
myFunc("one string");
myFunc(["one string", "second", "third"]);
concat seems to be one of the oldest methods of Array (even IE 5.5 knows it well).

Detect if object is either an Array or typed array

I need to determine whether a given object is either an Array, or typed array such as Float32Array.
Currently I'm checking whether the .length property is defined, but this isn't always indicative of an array. Similar issues arise with existence checking of .forEach() or other methods.
Several instanceof checks would suffice, as done here - but I'm wondering if there is a simple built-in feature, e.g., a generic Array.isArray() function that does what I need.
function isArrayOrTypedArray(x) {
return Boolean(x && (typeof x === 'object') && (Array.isArray(x) || (ArrayBuffer.isView(x) && !(x instanceof DataView)));
}
Unfortunately, I don't believe there is.
You can do the instanceof checks you mentioned, or you could check the result of Object.prototype.toString.call(variable) to see if it's one of the predefind strings ("[object Array]", "[object Uint8Array]", etc.). (Edit: Ah, I see by following the link in your question that that's also demonstrated by that code.)
While I think, as T.J. Crowder already said, there's no built-in function, you should be able to combine Array.isArray and ArrayBuffer.isView to get the functionality you want:
function isArrayOrTypedArray(x) {
return Array.isArray(x) || (ArrayBuffer.isView(x) &&
Object.prototype.toString.call(x) !== "[object DataView]");
}
Array.isArray(x) returns true if x is an array. ArrayBuffer.isView(x)returns true if x is a typed array or DataView, so we just need to ignore the case where x is a DataView to get the function we want.
Demonstration:
function isArrayOrTypedArray(x) {
return Array.isArray(x) || (ArrayBuffer.isView(x) && Object.prototype.toString.call(x) !== "[object DataView]");
}
console.log(isArrayOrTypedArray()); // false
console.log(isArrayOrTypedArray({})); // false
console.log(isArrayOrTypedArray(null)); // false
console.log(isArrayOrTypedArray(undefined)); // false
console.log(isArrayOrTypedArray(new ArrayBuffer(10))); // false
console.log(isArrayOrTypedArray([])); // true
console.log(isArrayOrTypedArray([1,2,3,4,5])); // true
console.log(isArrayOrTypedArray(new Uint8Array())); // true
console.log(isArrayOrTypedArray(new Float32Array())); // true
console.log(isArrayOrTypedArray(new Int8Array(10).subarray(0, 3))); // true
var buffer = new ArrayBuffer(2);
var dv = new DataView(buffer);
console.log(isArrayOrTypedArray(dv)); // false
You could do something like this:
function isArray(array) {
if((array.length || array.length === 0) && (array.constructor !== String)) {
return true;
}
return false;
}
Note that a String also has a length property and we need to exclude that, hence the constructor check.
To determine if x is an ArrayBuffer,
You can take advantage of the fact that new DataView(x) throws "TypeError: First argument to DataView constructor must be an ArrayBuffer" if x isn't an ArrayBuffer.
In other words, simply do:
function isArrayBuffer(x) {
try {
new DataView(x);
return true;
}
catch (TypeError) {
return false;
}
}
And to test if a thing is a TypedArray,
I believe ArrayBuffer.isView does the job.
You can use obj.constructor.name as a way of getting the object's name rather than an instanceof matching ladder.
What all these arrays have in common is they have Array in their classname and a array.length property which is a number.
function isArray(x) {
if (typeof x.length === 'number'
&& x.constructor.name.includes('Array')) {
return true;
}
return false;
}
This works in later versions of Javascript/Node anyway.
You could use name.includes if your JS version supports it.
Array.constructor.name is Array for [] and others like Uint8Array for typed arrays.
In other versions of JavaScript you may need obj.prototype.constructor.name.

Javascript Deep Comparison

Questions about deep comparison of objects have been asked, and I have the solution. But there is a line in the solution that I don't completely understand.
This is the solution, it is a question in Ch 4 of Eloquent JS to compare objects. I get all of it except the line:
if (!(prop in a) || !deepEqual(a[prop], b[prop]))
It is found toward the bottom. Here is full function:
function deepEqual(a, b) {
if (a === b) return true;
if (a == null || typeof a != "object" ||
b == null || typeof b != "object")
return false;
var propsInA = 0, propsInB = 0;
for (var prop in a)
propsInA += 1;
for (var prop in b) {
propsInB += 1;
if (!(prop in a) || !deepEqual(a[prop], b[prop]))
return false;
}
return propsInA == propsInB;
}
Is if (!(prop in a) comparing the existence of a property in 'b' at that index, or is it comparing values?
Really the same q. about the second half, but I know it's a different answer: what type of comparison is !deepEqual(a[prop], b[prop]) making (e.g., true or false)? I understand the recursion, but as in my previous question, is this making an 'it exists' comparison, or a comparison of the information in the values?
Thank you in advance.
According to MDN:
The in operator returns true if the specified property is in the specified object.
Also:
The for...in statement iterates over the enumerable properties of an object, in arbitrary order. For each distinct property, statements can be executed.
So to answer your first question, prop in a is checking whether prop, a field from object b, exists in object a.
To answer your second question, deepEqual(a[prop], b[prop]) is checking whether the object a[prop] and b[prop] are equal including all its children and contents.
The in operator returns true if the object on the right of the expression contains a key with the value of the string on the left of the expression.
Eg: prop in a is true if a contains a key with the same name as the string value of prop. Eg:
var prop = "age";
var obj1 = {
name: "Dave",
age: 21,
record: { ... }
};
var obj2 = {
name: "John",
record: { ... }
};
console.log(prop in obj1); // true
console.log(prop in obj2); // false
If prop was set to "record" then deepEqual(a[prop], b[prop]) recursively compares the values in a.record and b.record.
It's checking for existence. It says: "For each property in B, examine A. If A doesn't have the property, or if the value of the property on A doesn't deeply equal the value of the property on B, return false".
An alternate implementation would avoid the existence check on that line, and instead use if (typeof A == 'undefined') at the top of the function to validate the input parameters at the beginning of each round of recursion...at a glance, I think that'd be equivalent, but less efficient. The current implementation avoids the invocation on the undefined property.

My == isn't working [duplicate]

This question already has answers here:
How to compare arrays in JavaScript?
(55 answers)
Closed 6 years ago.
var a = [1, 2, 3];
var b = [3, 2, 1];
var c = new Array(1, 2, 3);
alert(a == b + "|" + b == c);
demo
How can I check these array for equality and get a method which returns true if they are equal?
Does jQuery offer any method for this?
This is what you should do. Please do not use stringify nor < >.
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: bugfix for option4: no total ordering on js objects (even excluding NaN!=NaN and '5'==5 ('5'===5, '2'<3, etc.)), so cannot use .sort(cmpFunc) on Map.keys() (though you can on Object.keys(obj), since even 'numerical' keys are strings).]
Option 1
Easiest option, works in almost all cases, except that null!==undefined but they both are converted to JSON representation null and considered equal:
function arraysEqual(a1,a2) {
/* WARNING: arrays must not contain {objects} or behavior may be undefined */
return JSON.stringify(a1)==JSON.stringify(a2);
}
(This might not work if your array contains objects. Whether this still works with objects depends on whether the JSON implementation sorts keys. For example, the JSON of {1:2,3:4} may or may not be equal to {3:4,1:2}; this depends on the implementation, and the spec makes no guarantee whatsoever. [2017 update: Actually the ES6 specification now guarantees object keys will be iterated in order of 1) integer properties, 2) properties in the order they were defined, then 3) symbol properties in the order they were defined. Thus IF the JSON.stringify implementation follows this, equal objects (in the === sense but NOT NECESSARILY in the == sense) will stringify to equal values. More research needed. So I guess you could make an evil clone of an object with properties in the reverse order, but I cannot imagine it ever happening by accident...] At least on Chrome, the JSON.stringify function tends to return keys in the order they were defined (at least that I've noticed), but this behavior is very much subject to change at any point and should not be relied upon. If you choose not to use objects in your lists, this should work fine. If you do have objects in your list that all have a unique id, you can do a1.map(function(x)}{return {id:x.uniqueId}}). If you have arbitrary objects in your list, you can read on for option #2.)
This works for nested arrays as well.
It is, however, slightly inefficient because of the overhead of creating these strings and garbage-collecting them.
Option 2
Historical, version 1 solution:
// 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
-->
Option 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.
-->
Option 4:
(continuation of 2016 edit)
This should work with most objects:
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
Note that if one is using the == notion of equality, then know that falsey values and coercion means that == equality is NOT TRANSITIVE. For example ''==0 and 0=='0' but ''!='0'. This is relevant for Sets: I do not think one can override the notion of Set equality in a meaningful way. If one is using the built-in notion of Set equality (that is, ===), then the above should work. However if one uses a non-transitive notion of equality like ==, you open a can of worms: Even if you forced the user to define a hash function on the domain (hash(a)!=hash(b) implies a!=b) I'm not sure that would help... Certainly one could do the O(N^2) performance thing and remove pairs of == items one by one like a bubble sort, and then do a second O(N^2) pass to confirm things in equivalence classes are actually == to each other, and also != to everything not thus paired, but you'd STILL have to throw a runtime error if you have some coercion going on... You'd also maybe get weird (but potentially not that weird) edge cases with https://developer.mozilla.org/en-US/docs/Glossary/Falsy and Truthy values (with the exception that NaN==NaN... but just for Sets!). This is not an issue usually with most Sets of homogenous datatype.
To summarize the complexity of recursive equality on Sets:
Set equality is the tree isomorphism problem http://logic.pdmi.ras.ru/~smal/files/smal_jass08_slides.pdf but a bit simpler
set A =? set B being synonymous with B.has(k) for every k in A implicitly uses ===-equality ([1,2,3]!==[1,2,3]), not recursive equality (deepEquals([1,2,3],[1,2,3]) == true), so two new Set([[1,2,3]]) would not be equal because we don't recurse
trying to get recursive equality to work is kind of meaningless if the recursive notion of equality you use is not 1) reflexive (a=b implies b=a) and 2) symmetric (a=a) and 3) transitive (a=b and b=c implies a=c); this is the definition of an equivalence class
the equality == operator obviously does not obey many of these properties
even the strict equality === operator in ecmascript
does not obey these properties, because the strict equality comparison algorithm of ecmascript has NaN!=NaN; this is why many native datatypes like Set and Map 'equate' NaNs to consider them the same values when they appear as keys
As long as we force and ensure recursive set equality is indeed transitive and reflexive and symmetric, we can make sure nothing horribly wrong happens.
Then, we can do O(N^2) comparisons by recursively comparing everything randomly, which is incredibly inefficient. There is no magical algorithm that lets us do setKeys.sort((a,b)=> /*some comparison function*/) because there is no total ordering in ecmascript (''==0 and 0=='0', but ''!='0'... though I believe you might be able to define one yourself which would certainly be a lofty goal).
We can however .toStringify or JSON.stringify all elements to assist us. We will then sort them, which gives us equivalence classes (two same things won't not have the same string JSON representation) of potentially-false-positives (two different things may have the same string or JSON representation).
However, this introduces its own performance issues because serializing the same thing, then serializing subsets of that thing, over and over, is incredibly inefficient. Imagine a tree of nested Sets; every node would belong to O(depth) different serializations!
Even if that was not an issue, the worst-case performance would still be O(N!) if all the serializations 'hints' were the same
Thus, the above implementation declares that Sets are equal if the items are just plain === (not recursively ===). This will mean that it will return false for new Set([1,2,3]) and new Set([1,2,3]). With a bit of effort, you may rewrite that part of the code if you know what you're doing.
(sidenote: Maps are es6 dictionaries. I can't tell if they have O(1) or O(log(N)) lookup performance, but in any case they are 'ordered' in the sense that they keep track of the order in which key-value pairs were inserted into them. However, the semantic of whether two Maps should be equal if elements were inserted in a different order into them is ambiguous. I give a sample implementation below of a deepEquals that considers two maps equal even if elements were inserted into them in a different order.)
(note [1]: IMPORTANT: NOTION OF EQUALITY: You may want to override the noted line with a custom notion of equality, which you'll also have to change in the other functions anywhere it appears. For example, do you or don't you want NaN==NaN? By default this is not the case. There are even more weird things like 0=='0'. Do you consider two objects to be the same if and only if they are the same object in memory? See https://stackoverflow.com/a/5447170/711085 . You should document the notion of equality you use.) Also note that other answers which naively use .toString and .sort may sometimes fall pray to the fact that 0!=-0 but are considered equal and canonicalizable to 0 for almost all datatypes and JSON serialization; whether -0==0 should also be documented in your notion of equality, as well as most other things in that table like NaN, etc.
You should be able to extend the above to WeakMaps, WeakSets. Not sure if it makes sense to extend to DataViews. Should also be able to extend to RegExps probably, etc.
As you extend it, you realize you do lots of unnecessary comparisons. This is where the type function that I defined way earlier (solution #2) can come in handy; then you can dispatch instantly. Whether that is worth the overhead of (possibly? not sure how it works under the hood) string representing the type is up to you. You can just then rewrite the dispatcher, i.e. the function deepEquals, to be something like:
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);
}
jQuery does not have a method for comparing arrays. However the Underscore library (or the comparable Lodash library) does have such a method: isEqual, and it can handle a variety of other cases (like object literals) as well. To stick to the provided example:
var a=[1,2,3];
var b=[3,2,1];
var c=new Array(1,2,3);
alert(_.isEqual(a, b) + "|" + _.isEqual(b, c));
By the way: Underscore has lots of other methods that jQuery is missing as well, so it's a great complement to jQuery.
EDIT: As has been pointed out in the comments, the above now only works if both arrays have their elements in the same order, ie.:
_.isEqual([1,2,3], [1,2,3]); // true
_.isEqual([1,2,3], [3,2,1]); // false
Fortunately Javascript has a built in method for for solving this exact problem, sort:
_.isEqual([1,2,3].sort(), [3,2,1].sort()); // true
For primitive values like numbers and strings this is an easy solution:
a = [1,2,3]
b = [3,2,1]
a.sort().toString() == b.sort().toString()
The call to sort() will ensure that the order of the elements does not matter. The toString() call will create a string with the values comma separated so both strings can be tested for equality.
With JavaScript version 1.6 it's as easy as this:
Array.prototype.equals = function( array ) {
return this.length == array.length &&
this.every( function(this_i,i) { return this_i == array[i] } )
}
For example, [].equals([]) gives true, while [1,2,3].equals( [1,3,2] ) yields false.
Even if this would seem super simple, sometimes it's really useful. If all you need is to see if two arrays have the same items and they are in the same order, try this:
[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
However, this doesn't work for mode advanced cases such as:
[[1,2],[3]].toString() == [[1],[2,3]].toString()
true
It depends what you need.
Based on Tim James answer and Fox32's comment, the following should check for nulls, with the assumption that two nulls are not equal.
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
jQuery has such method for deep recursive comparison.
A homegrown general purpose strict equality check could look as follows:
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;
};
Tests:
// 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.');
If you are using lodash and don't want to modify either array, you can use the function _.xor(). It compares the two arrays as sets and returns the set that contains their difference. If the length of this difference is zero, the two arrays are essentially equal:
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
Check every each value by a for loop once you checked the size of the array.
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;
}
}
Using map() and 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)
);
}
If you wish to check arrays of objects for equality and order does NOT matter, i.e.
areEqual([{id: "0"}, {id: "1"}], [{id: "1"}, {id: "0"}]) // true
you'll want to sort the arrays first. lodash has all the tools you'll need, by combining sortBy and isEqual:
// 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))
}
EDIT: Since sortBy returns a new array, there is no need to clone your arrays before sorting. The original arrays will not be mutated.
Note that for lodash's isEqual, order does matter. The above example will return false if sortBy is not applied to each array first.
This method sucks, but I've left it here for reference so others avoid this path:
Using Option 1 from #ninjagecko worked best for me:
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
It will also handle the null and undefined case, since we're adding this to the prototype of array and checking that the other argument is also an array.
There is no easy way to do this. I needed this as well, but wanted a function that can take any two variables and test for equality. That includes non-object values, objects, arrays and any level of nesting.
In your question, you mention wanting to ignore the order of the values in an array. My solution doesn't inherently do that, but you can achieve it by sorting the arrays before comparing for equality
I also wanted the option of casting non-objects to strings so that [1,2]===["1",2]
Since my project uses UnderscoreJs, I decided to make it a mixin rather than a standalone function.
You can test it out on http://jsfiddle.net/nemesarial/T44W4/
Here is my 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;
}
});
This is how you use it:
_.equal([1,2,3],[1,2,"3"],true)
To demonstrate nesting, you can do this:
_.equal(
['a',{b:'b',c:[{'someId':1},2]},[1,2,3]],
['a',{b:'b',c:[{'someId':"1"},2]},["1",'2',3]]
,true);
It handle all possible stuff and even reference itself in structure of object. You can see the example at the end of code.
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);
*/
var a= [1, 2, 3, '3'];
var b = [1, 2, 3];
var c = a.filter(function (i) { return ! ~b.indexOf(i); });
alert(c.length);

Checking if an object is array-like

Is there a way to check if an object is "array-like", like for these types of objects:
Arrays (duh)
Typed Arrays (Uint8Array, etc), these will return false when Array.isArray is used
arguments object
NodeLists*
There are a few other ones that I can't think of off-hand
I suppose you could check for the presence of a .length property, but non-array-like objects can contain the .length property. I guess the thing these all share in common is the array accessor.
As best I've found in my research on this topic, you have only a couple choices:
You can look only at the .length property and accept any object that seems to have an appropriate .length property that isn't any other things you know you should eliminate (like a function).
You can check for specific array-like objects (HTMLCollection, nodeList) and bias in favor of them.
Here are two options for the first method - one that doesn't accept a zero length and one that does (these incorporate suggestions by gilly3 and things we see in jQuery's similar function):
// see if it looks and smells like an iterable object, but don't accept length === 0
function isArrayLike(item) {
return (
Array.isArray(item) ||
(!!item &&
typeof item === "object" &&
item.hasOwnProperty("length") &&
typeof item.length === "number" &&
item.length > 0 &&
(item.length - 1) in item
)
);
}
This, of course, reports false for items with .length === 0, If you want to allow .length === 0, then the logic can be made to include that case too.
// see if it looks and smells like an iterable object, and do accept length === 0
function isArrayLike(item) {
return (
Array.isArray(item) ||
(!!item &&
typeof item === "object" &&
typeof (item.length) === "number" &&
(item.length === 0 ||
(item.length > 0 &&
(item.length - 1) in item)
)
)
);
}
Some test cases: http://jsfiddle.net/jfriend00/3brjc/
2) After checking to see that it's not an actual array, you can code to check for specific kinds of array-like objects (e.g. nodeList, HTMLCollection).
For example, here's a method I use when I want to make sure I include nodeList and HTMLCollection array-like objects:
// assumes Array.isArray or a polyfill is available
function canAccessAsArray(item) {
if (Array.isArray(item)) {
return true;
}
// modern browser such as IE9 / firefox / chrome etc.
var result = Object.prototype.toString.call(item);
if (result === "[object HTMLCollection]" || result === "[object NodeList]") {
return true;
}
//ie 6/7/8
if (typeof item !== "object" || !item.hasOwnProperty("length") || item.length < 0) {
return false;
}
// a false positive on an empty pseudo-array is OK because there won't be anything
// to iterate so we allow anything with .length === 0 to pass the test
if (item.length === 0) {
return true;
} else if (item[0] && item[0].nodeType) {
return true;
}
return false;
}
You can check if the object is iterable :
function isIterable(o){
return (o!=null && typeof(o[Symbol.iterator])==='function');
}
Careful though, returns true for strings. If that's a problem, exclude them :
function isIterable(o){
return (o!=null && typeof(o[Symbol.iterator])==='function' && typeof(o)!=='string');
}
Then either access the elements using the iterator or if you want to use the regular array[0] way, just add a check for length property.
Complete isArrayLike function :
function isArrayLike(a){
return (
a!=null &&
typeof(a[Symbol.iterator])==='function' &&
typeof(a.length)==='number' &&
typeof(a)!=='string'
);
}
In JavaScript an array-like object is an object containing a length property typically of type number with a non-negative whole value no greater than 2^53-1. Array-like objects with lengths greater than zero also typically contain additional properties beginning at [0] and going up to [n-1] where n = the value of length.
From MDN:
The term array-like object refers to any object that doesn't throw during the length conversion process described [below]. In practice, such object is expected to actually have a length property and to have indexed elements in the range 0 to length - 1. (If it doesn't have all indices, it will be functionally equivalent to a sparse array.)
Normalization of the length property
The length property is converted to an integer and then clamped to the range between 0 and 2^53 - 1.
Array-like objects also differ from other objects in that Array methods such as Array.prototype.push can be called indirectly on them using Function.prototype.call().
Also from MDN:
Array methods cannot be called directly on array-like objects. But you
can call them indirectly using Function.prototype.call().
Calling Array functions on an object without a normalized length property changes the value of length. Therefore I thought it best that my function return false if the length property is not already an integer between 0 and 2^53 - 1. The length property cannot be a BigInt as that would throw a TypeError.
This function also checks for existence of the last element to help rule out objects that have a length property but aren't intended to be array-like.
Tested and works in old browsers (ES3 and up).
function isArrayLike(obj) {
var i;
return /( Arguments|NodeList|Collection|Array)\b/.test(({}).toString.call(obj))// Always array-like
// Confirm obj is an object but not a function or null
|| !!obj && typeof obj=="object"
// Confirm that Array methods can be called indirectly on obj
// without throwing or changing the value of obj.length
// by confirming obj.length (i) is a normalized integer
// Usually this means i is in the range 0 - 9007199254740991
&& [].push.call({length:Number(i=obj.length)})===i
// And when non-zero confirm i>0 and i-1 is a property in obj
&& (!i || i>0 && i-1 in obj);
}
Well, it depends what you mean by array-like. Probably something you could iterate with a for loop like this:
for (var i=0, l=obj.length; i<l; ++i) {
var item = obj[i];
}
So then the test is simple:
function isArrayLike(obj) {
if (!obj) return false;
var l = obj.length;
if (typeof l != 'number' || l < 0) return false;
if (Math.floor(l) != l) return false;
// fast check
if (l>0 && !((l-1) in obj)) return false;
// more complete check (optional)
for (var i=0; i<l; ++i) {
if (!(i in obj)) return false;
}
return true;
}
Of course, this won't catch arrays which are sparsely populated, but then again, are they really being used as arrays? NodeLists and the like won't be sparsely populated.
Enjoy!
There is a way to check if an object is array-like or not, even if there are no elements in it, using this function here:
isArrayLike = function (_) {
_[0] = 0; return [].slice.call(_).length >= Object.values(_).length;
}
This uses a little hack I accidentally discovered that allows you to determine if an object is (1) an array, (2) array-like, or (3) object / object-like.
The only downside is that it does not work correctly for array-likes that have object-like properties added, such as arguments
Technically, (pretty much) every object is "array-like" (because of type coercion of undefined) according to the standard (ECMAScript 2015 Language Specification §7.3.17, CreateListFromArrayLike (obj [, elementTypes] )):
7.3.17 CreateListFromArrayLike (obj [, elementTypes] )
The abstract operation CreateListFromArrayLike is used to create a List value whose elements are provided by the indexed properties of an array-like object, obj. The optional argument elementTypes is a List containing the names of ECMAScript Language Types that are allowed for element values of the List that is created. This abstract operation performs the following steps:
ReturnIfAbrupt(obj).
If elementTypes was not passed, let elementTypes be (Undefined, Null, Boolean, String, Symbol, Number, Object).
If Type(obj)
is not Object, throw a TypeError exception.
Let len be ToLength(Get(obj, "length")).
ReturnIfAbrupt(len).
Let list be an empty List.
Let index be 0.
Repeat while index < len
Let indexName be ToString(index).
Let next be Get(obj, indexName).
ReturnIfAbrupt(next).
If Type(next)
is not an element of elementTypes, throw a TypeError exception.
Append next as the last element of list.
Set index to index + 1.
Return list.
Generated via https://www.browserling.com/tools/html-to-markdown
An Array is a value that has following properties:
Its of type object
It has length property which is equal or greater than 0.
All the value based keys are numeric and is greater than or equal to 0.
Exceptions:
length
Value is a function.
function isArrayLike(value) {
if (typeof value === "object" && !!value) {
const isArray = Array.isArray(value);
const allNumericKeys = Object.keys(value).every((k) =>
(!isNaN(k) && +k >= 0) ||
k === "length" ||
typeof value[k] === "function"
)
const hasLength = value.length > 0
return isArray || (allNumericKeys && hasLength)
}
return false
}
console.log('Array: ', isArrayLike([]))
console.log('Array Like: ', isArrayLike({1: 'a', 2: 'b', length: 3}))
console.log('Array Like with function: ', isArrayLike({1: 'a', 2: 'b', length: 3, foo: () => {} }))
console.log('Array Like with negative keys: ', isArrayLike({ "-1": 'a', "-2": 'b', length: 1}))
console.log('Array Like without length:', isArrayLike({1: 'a', 2: 'b' }))
console.log('Node List: ', isArrayLike(document.querySelectorAll('p')))
console.log('null: ', isArrayLike(null))
console.log('String: ', isArrayLike('Test'))
console.log('Number: ', isArrayLike(123))
<div>
<p></p>
<p></p>
<p></p>
<p></p>
</div>
I say nothing beats the simplicity and expressiveness of extending native objects:
Object.prototype.isArrayLike = function(){ return false; };
Array.prototype.isArrayLike = function(){ return true; };
NodeList.prototype.isArrayLike = function(){ return true; };
HTMLCollection.prototype.isArrayLike = function(){ return true; };
This approach could cause conflicts between frameworks, however, I recommend keeping your distance from a framework whose isArrayLike function does not what the name suggests.

Categories

Resources