Thankfully, ECMAScript 5 introduced Array.isArray()
back in December 2009. If for some reason, you are using a version of JavaScript older than ECMAScript 5, please upgrade.
If you insist on it, though, then arrays do have certain properties that differentiate them from any other type. Properties that I haven't seen mentioned in any of the other answers. Let's get into some JavaScript politics.
An array is an object (typeof [] === "object"
), but unlike traditional objects, they have a length property (typeof ( {} ).length === "undefined"
). null
is also an object (typeof null === "object"
), but you can't access a property of null
because null
is not an object.
This is a bug in the specification that goes all the way back to the very beginning of JavaScript, when objects had the type tag 0
and null
was represented as a literal null pointer 0x00
, which caused the interpreter to confuse it with objects.
Unfortunately, this doesn't account for []
vs. {length:0}
. So we must now turn to the prototype chain.
( [] ).__proto__ === Array.prototype && ( [] ).__proto__ !== Object.prototype
.
Thus, without Array.isArray()
, this is just about the closest we can get:
function is_array(array){
return array !== null
&& typeof array === "object"
&& array.__proto__ === Array.prototype;
}
[ [], [1,2,3], {length: 0}, {},
1, 0, Infinity, NaN, "1", "[1,2,3]",
null, undefined, [null], [undefined], {a:[]},
[{}], [{length: 0}], [Infinity], [NaN],
{__proto__: Array.prototype}
].filter(is_array)
// Expected: [ [], [1,2,3], [null], [undefined], [{}], [{length: 0}], [Infinity], [NaN] ]
// Actual: [ [], [1,2,3], [null], [undefined], [{}], [{length: 0}], [Infinity], [NaN], {__proto__: Array.prototype} ]
The object maliciously designed to look just like an array actually passes the Turing test. However, replacing the prototype chain with the Array prototype chain is enough to make it act just like an array, effectively making it an array.
The only thing in the world that can tell such an object is actually not an array, is Array.isArray()
. But for the purposes you would usually be checking if an object is an array, said object should play nice with your code.
Even the behavior when you change the length of the array artificially is the same: if the length is longer than the number of elements in the array, you will have "empty slots" of that special "implicit undefined" type that is somehow distinct from undefined while also being === undefined
; the very same type that is the reason we use typeof obj !== "undefined"
to avoid throwing a ReferenceError
because obj === undefined
only doesn't throw an error if obj
was explicitly defined as undefined
.
a = {__proto__: Array.prototype}; // Array {}
a.push(5)
a // [5]
a.length = 5
a // [5, empty x 4]
b = a.map(n => n*n) // [25, empty x 4]
b.push(undefined)
b.push(undefined)
b // [25, empty x 4, undefined, undefined]
b[1] // undefined
b[1] === b[5] // true
Array.isArray(a) // false
Array.isArray(b) // true
Don't use is_array()
, though. It's one thing to reinvent the wheel for learning purposes. It's another thing to do it in production code. Don't even use it as a polyfill. Supporting old JavaScript versions means supporting old browsers means encouraging the use of insecure software means putting the user at risk for malware.
arr.constructor === Array
is fastest. – Parkearr instanceof Array
? – Kneelprototype
test doesn't actually test anything. – Possumtypeof x === "string"
to see if it's a string, and if not you can assume it's an array for your use case – Addingtonarr.constructor === Array
test will return false.Array.isArray(arr)
still returns true though. – Pineroarr.constructor
approach. Some considerations in this post – Hallette