JavaScript - Difference between Array and Array-like object
What is it?
An Object which has a length property of a non-negative Integer, and usually some indexed properties. For example
var ao1 = {length: 0}, // like []
ao2 = {0: 'foo', 5: 'bar', length: 6}; // like ["foo", undefined × 4, "bar"]
You can convert Array-like Objects to their Array counterparts using Array.prototype.slice
var arr = Array.prototype.slice.call(ao1); // []
Whats the difference between it and a normal array?
It's not constructed by Array
or with an Array literal []
, and so (usually) won't inherit from Array.prototype
. The length property will not usually automatically update either.
ao1 instanceof Array; // false
ao1[0] = 'foo';
ao1.length; // 0, did not update automatically
Whats the difference between an array-like object and a normal object?
There is no difference. Even normal Arrays are Objects in JavaScript
ao1 instanceof Object; // true
[] instanceof Object; // true
Creating array-like objects in JavaScript
Depends specifically on the console. For custom objects in Chrome's developer console, and Firebug you'll need both the length
and splice
properties. splice
will also have to be a function.
a = {
length: 0,
splice: function () {}
}
console.log(a); //[]
It's important to note, however, that there is no official standard.
The following code is used by jQuery (v1.11.1) internally to determine if an object should use a for
loop or a for..in
loop:
function isArraylike( obj ) {
var length = obj.length,
type = jQuery.type( obj );
if ( type === "function" || jQuery.isWindow( obj ) ) {
return false;
}
if ( obj.nodeType === 1 && length ) {
return true;
}
return type === "array" || length === 0 ||
typeof length === "number" && length > 0 && ( length - 1 ) in obj;
}
Note that it's possible to have an object that appears in the console as an array ([]
) but that gets iterated over with a for..in
loop in jQuery, or an object that appears as an object in the console ({}
) but that gets iterated over with a for
loop in jQuery.
Is array-like object a datatype
An "array-like object" is any object that has numerical indices and a length
property indicating how many values it contains.
"Array-like object" is not a datatype, but simply a category for certain objects that fit that description. For example, the following is an array-like object, and it derives directly from Object
, not anything more specific:
var alo = {
0: "hello",
1: 3,
length: 2
};
You can often use certain array operations on array-like objects and they will still work as though they were arrays. In particular, you can use the slice()
method to convert an array-like object into an actual array:
var a = Array.prototype.slice.call(alo); // a is an actual array with the values
// "hello" and 3
and you can use certain array methods directly on array-like objects without first converting them to arrays. The following prints out "hello" and "3":
Array.prototype.forEach.call(alo, function (v) { console.log(v); })
Convert Array-like objects, Array.prototype.slice or Array.from
Array.prototype.slice.call
has been the long-standing mechanism for converting array-like objects to arrays. If you are looking for browser compatibility use this (although it appears that on some older browsers like IE8 and below this will not work at all).
Array.from
was introduced ECMA6 in June of 2015. It accomplishes the same thing as the prior mechanism, only in a more fluent and concise manner. In addition Array.from
can convert more structures into arrays such as generators.
Learn about array.from
Checking if an object is array-like
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;
}
Javascript. Creating Array-like objects. Is it possible to make .length autoincrement?
is it possible somehow to add autoincrement .length property
Not really; at least, you can't do all the things Array
does around length
and index properties.
In an ES5-enabled environment, you can make length
a property with getter and setter functions. But it's awkward to find out what length
should be, because there's (so far) no "catch all" way of setting it when code like:
arr[1] = "foo";
...runs. (ES6 may well make it possible to have catch-all property setters, via proxies.)
So your length
would have to figure it out from the object contents, which is not going to be very efficient, e.g.:
var explicitLength;
Object.defineProperty(this, "length", {
get: function() {
var length;
// Find our length from our max index property
length = Object.keys(this).reduce(function(maxIndex, key) {
var index = key === "" ? NaN : +key;
if (!isNaN(index) && index > maxIndex) {
return index;
}
return maxIndex;
}, -1) + 1;
// If we have an explicitly-set length, and it's greater,
// use that instead. Note that if explicitLength is
// `undefined`, the condition will always be false.
if (explicitLength > length) {
length = explicitLength;
}
return length;
},
set: function(value) {
explicitLength = value;
// You might choose to have code here removing properties
// defining indexes >= value, likes Array does
}
});
Note that even when we have an explicitly-set length, we still have to check to see if the natural length is greater, to deal with:
arr.length = 2;
arr[5] = "foo";
console.log(arr.length); // Should be 6, not 2
Doing that much work on a property retrieval is obviously not a good thing, so I would steer clear of this sort of thing.
Above I said "...so I would steer clear of this sort of thing."
Well that's all very well and good, but what should you do instead?
Suppose you have the functions nifty
and spiffy
that you want to have available. You have two realistic options:
Use an actual array, and mix in your custom methods to each array instance
Enhance
Array.prototype
#1 Use an actual array and mix in your methods
You'd define your methods on a convenient object, say ArrayMixin
:
var ArrayMixin = {
nifty: function() {
// ...do something nifty with `this`
},
spiffy: function() {
// ...do something spiffy with `this`
}
};
Then have a builder function for creating arrays:
function createArray(arr) {
arr = arr || []; // `arr` is optional
extend(arr, ArrayMixin);
return arr;
}
What's this extend
function, you ask? Something that copies properties from one object to another. If you use any libraries or frameworks (jQuery, Underscore, Angular, PrototypeJS, ...), they're likely to have an extend
function of some kind that does this. The usually look something like this (this is very off-the-cuff).
function extend(target) {
var arg, obj, key;
for (arg = 1; arg < arguments.length; ++arg) {
obj = arguments[arg];
for (key in obj) {
if (obj.hasOwnProperty(key)) {
target[key] = obj[key];
}
}
}
return target;
}
Note that that's a shallow copy, and it doesn't bother to try to make the properties it adds to target
non-enumerable. Tweak as desired.
So then when you want an array:
var a = createArray(['one', 'two', 'three']);
...and a
has nifty
and spiffy
on it.
Example:
var ArrayMixin = { nifty: function() { this.forEach(function(entry, index) { this[index] = entry.toUpperCase(); }, this); return this; }, spiffy: function() { this.forEach(function(entry, index) { this[index] = entry.toLowerCase(); }, this); return this; }};
function createArray(arr) { arr = arr || []; // `arr` is optional extend(arr, ArrayMixin); return arr;}
function extend(target) { var arg, obj, key; for (arg = 1; arg < arguments.length; ++arg) { obj = arguments[arg]; for (key in obj) { if (obj.hasOwnProperty(key)) { target[key] = obj[key]; } } } return target;}
var a = createArray(['one', 'two', 'three']);a.nifty();snippet.log(a.join(", "));a.spiffy();snippet.log(a.join(", "));
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 --><script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
Related Topics
How to Sort/Order Keys in JavaScript Objects
Pagination on a List Using Ng-Repeat
How to Import a CSS File in a React Component
How to Remove Leading and Trailing White Spaces from a Given HTML String
How to Request the Garbage Collector in Node.Js to Run
How to Access Accelerometer/Gyroscope Data from JavaScript
Regex for Number with Decimals and Thousand Separator
Moment.Js Transform to Date Object
How to Beautify JavaScript Code Using Command Line
How to Skip Over an Element in .Map()
Passing Variables to the Next Middleware Using Next() in Express.Js
Get Character Value from Keycode in JavaScript... Then Trim
Random Number Generator Without Dupes in JavaScript
Typescript Compile to Single File
Run JavaScript in Visual Studio Code
Remove Characters from a String
$.Each() VS For() Loop - and Performance
How to Make a Div Element Editable (Like a Textarea When I Click It)