Array Like Objects in JavaScript

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:

  1. 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).

  2. 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:

  1. Use an actual array, and mix in your custom methods to each array instance

  2. 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



Leave a reply



Submit