Understanding JavaScript Arrays

(Russian Version is here)

What is an Array in JavaScript?

A numerically indexed map of values.

Traditionally an array reserves a continuous allocation of memory of predefined length. In JavaScript this is not the case. A JavaScript array is simply a glorified object with a unique constructor and literal syntax and an additional set of properties and methods inherited from Array.prototype. If this means we make a small sacrifice in performance, it is more than compensated for by its ease of use and the power of its utilities. Unlike its counterparts in certain other languages, JavaScript arrays are a joy to use – this is something they definitely got right.

How do I create a JavaScript Array?

Lets start with the best way. In JavaScript, whenever there is a literal syntax for object creation it generally makes sense to use it.

//create a new, empty array
var a = [];
//add members to an existing array
a[0] = "Bob";
a[1] = "Mary";
a[2] = "Joe";
//or simply use push
a.push("Jane");
a.push("Carlos");
//create a new array and predefine some members
var b = ["Bob", "Mary", "Joe", "Jane", "Carlos"];

Alternatively you could use the new Constructor syntax. Apart from the obvious drawback of 5-9 additional assaults on your dainty finger pads (the “new” part is effectively optional) there is a more serious issue around ambiguity of intention:

//create a new array with 8 undefined members
var a = new Array(8);
//create a new array containing two predefined elements
var b = new Array(8,9);
a.length; //8
b.length; //2
a[0]; //undefined
b[0]; //8

These two declarations look strikingly similar yet produce entirely different results. Moreover, lets say someone edits the second statement because they now only want to predefine one element, the number 8, in array b. It’s quite likely they might modify it to the following (and who could blame them?):

//create a new array containing one predefined element
var b = new Array(8); //Wrong!

Of course this doesn’t do what they wanted. The only way to predefine an array with one primitive number is to use the literal syntax. (Thanks Peter and Dmitry for clarification)

Is there any advantage to using the new Array syntax?

Well it means you can define the length of your array at creation time. But since JavaScript arrays do not require an up front allocation of memory, and they can be lengthened at will any time, that’s a questionable requirement. (Several people pointed out webkit et al have built in optimization when Array length predefined – though there is nothing in the spec to suggest this)

What types of data can an Array contain?

An array can contain any object or primitive type. Multiple data types can co-exist in the same array.

How do I access an Array element?

Array elements are simply object properties and are accessed in the same way as other object properties. Since property identifiers are always strings, the array index is also a string, not a number. However when you use subscript notation (square brackets) to access the property, a number literal can also be used since it will be coerced to a string by the interpreter. Dot notation accessors will not work for array member access because literal property identifiers can’t start with a number (again, all these behaviours derive from generic object property rules, they are not array-specific)

var a = ["banana", Math.min, 4, "apple"];
a['1']; //min()
a[2]; //4

How should I iterate over the elements of an array?

Typically it makes sense to use the standard for loop:

var a = ["banana", Math.min, 4, "apple"];
for (var i=0; i < a.length; i++) {
    console.log(a[i]);
}

If your array is a long one you may be worried about the additional cost of requesting array.length on every iteration. To workaround this you can define the array length up front:

var a = makeBigArray();
var aLength = a.length;
for (var i=0; i < aLength; i++) {
    console.log(a[i]);
}

Using a for…in statement for array iteration is not advised because you may also pick up enumerable properties from the prototype (see below)

Which properties are unique to Arrays?

The most important array property is length (strings and functions have length too but the array definition of length is unique)

ECMA specifies:
The length property of this Array object is always numerically greater than the name of every property whose name is an array index

In other words its (the numeric value of the last index) + 1

Array’s are not upper-bound. You can add an element at an index greater than (length – 1) and the length property will be modified based on the above definition. Array’s have a maximum length but its too big for you to worry about.

var a = [3,4,1];
a.length; //3
a[20] = 2;
a.length; //21
//element indexes 3-19 automatically created with value initialized to undefined
a[18]; //undefined

Arrays are lower-bound to zero. If you try to add a value at a negative index you will just be writing a regular object property (see also “associative arrays” below)

var a = [];
a[-1] = "giraffe";
a[-1]; //"giraffe"; //because still using a regular object property accessor
a.length; //0
a.toString(); //""

You can manipulate the contents of an existing array by updating its length value. If you reduce the length property of an existing array, members with indexes greater than or equal to the new length get discarded (this turns out to be the only way to remove indexes from an array – you can delete an element but this will only delete the value and leave the index in place – i.e. your array becomes “sparse” = it gets holes in it)

var a = [0,1,2,3,4,5,6];
a.length; //7
a.length = 5;
a.toString(); //"0,1,2,3,4"
a[6]; //undefined

Conversely if you increase the length of an existing array by n your array appears to get n new members, each with its value initialized to undefined – however as Dmitry Soshnikov points out, this is the standard response to accessing a non existent property. In reality nothing has changed except the array length.

var a = [0,1,2,3,4,5,6];
a.length; //7
a[9]; //undefined
a[59]; //undefined
a.length = 10;
a.toString(); //"0,1,2,3,4,5,6,,,"
a[9]; //undefined
a[59]; //undefined

There are two additional pseudo-properties of arrays: index and input. These properties are only present in arrays created by regular expression matches

Which methods are inherited from Array.prototype?

Array provides a plethora of very useful utilities, there are too many to go into great detail and you are probably familiar with most pre-ECMA 5 functions. The following array methods are available on the latest versions of all major browsers

concat shallow copy the array and append the arguments
join create a string from the array. Add the argument as glue between each array member
shift remove and return the first element
pop remove and return the last element
unshift append the arguments to the front of the array
push append the arguments to the end of the array
reverse reverses the array without copying it
slice shallow copy the portion of the array delimited by the index arguments
splice removes specified elements from the array, replaces them with optional additional arguments
sort sorts the array without copying it, optionally using a comparator argument
toString invokes join without passing an argument

ECMA 5 specifies an additional set of high-order functions all of which have already been implemented by all the major browsers except IE<=8 (but IE9 preview implements them). Many of these new methods will already be familiar to those of you who make use of the major JavaScript frameworks:

indexOf returns the first element equal to the specified value, or -1 if none found
lastIndexOf returns the last element equal to the specified value, or -1 if none found
every returns true if the supplied function returns true when applied to every element
some returns true if the supplied function returns true when applied to at least one element
forEach applies the supplied function to every element in the array
map creates a new array containing the results of applying the supplied function to every element in the array
filter creates a new array containing all the elements for which the supplied function returns true
reduce apply a function simultaneously against two values of the array (from left-to-right) so as to reduce it to a single value (note: reduce had a different meaning in older versions of Prototype.js)
reduceRight apply a function simultaneously against two values of the array (from right-to-left) so as to reduce it to a single value

How do I know if my object is of type Array?

The eternal question. The problem is that in JavaScript when you use typeof against an array it returns “object”. I nearly wrote an entire blog post on just this topic. Fortunately kangax already did just that. The upshot is this latest state-of-the-art version of isArray which is simpler and more robust than any of its slew of predecessors. It also happens to be the implementation currently used by both jQuery and Prototype.js

function isArray(o) {
  return Object.prototype.toString.call(o) === "[object Array]";
}

What about “Associative Arrays”?

JavaScript does not support Associative Arrays. This is a common misconception owing to the fact that the following appears to act like a non-numerically indexed Array.

var a = new Array();
a['cat'] = 4;
a['spider'] = 8;
a['centipede'] = 100;
a['spider']; //8

Actually this code, while not incorrect. is an inappropriate usage of the array object. No array members are added (array.length is 0). All we did was to set properties on a regular object – essentially we created a hash table.

To illustrate the point, we could substitute Array for any other object and get the same result.

var a = Boolean;
a['cat'] = 4;
a['spider'] = 8;
a['centipede'] = 100;
a['spider']; //8

Moreover building a hash over an Array object is potentially dangerous. If anyone extends Array.prototype with enumerable properties (as, for example, the Prototype.js library does) these will be read in during for…in iterations, wreaking havoc with your logic (or at least requiring you to make use of the clunky hasOwnProperty method).

Build hashes over Object and nothing else, since by convention Object.prototype is not augmented.

How is array behavior described in the ECMA standard?

Firstly ECMA specifies an Array.Prototype which imbues arrays with their unique properties. Secondly ECMA defines a specialized rules for property setting when applied to arrays.

An understanding of an object’s internal [[Get]] and [[Put]] methods might be helpful at this point. By specification, every JavaScript object has these methods – they are essentially the low level accessor functions by which the JavaScript Engine fetches and updates object properties. The [[Get]] method of an Array is no different from the [[Get]] method of Object – hence you access array members just as you would access any object property However the [[Put]] method of an Array is specialized and this is what makes an array unique.

Richard Cornford explains it well: “The Array’s [[Put]] method must be interested in the assignments to the – length – property of an array, because if it is less than the current value it may be necessary to delete properties from the array. Otherwise, the property name string argument is converted into a number using the internal ToUnit32 function, and if
that number is not less than the current value of the – length -property then the – length – property of the Array is re-set to the value of that number plus one.

Whenever the property name provided to the Array’s [[Put]] method is not a string representation of an (unsigned 32 bit) integer number clause 8 in the algorithm for the [[Put]] method avoids the need to consider interaction with the Array’s – length – property”

Further reading:

David Flanagan: JavaScript, The Definitive Guide (O’Reilly Press)
Patrick Hunlock: Mastering JavaScript Arrays
Andrew Dupont: JavaScript “Associative Arrays” Considered Harmful
Juriy Zaytsev (“kangax”): `instanceof` considered harmful (or how to write a robust `isArray`)
Juriy Zaytsev (“kangax”): How ECMAScript 5 still does not allow to subclass an array
Richard Cornford: (guest comment)
ECMA-262 5th Edition section 15.4
Mozilla Core JavaScript Reference: Array

About these ads

21 thoughts on “Understanding JavaScript Arrays

  1. Nice writeup. I’d like to clarify a few minor errors though…

    If you want to initialize a new array with a number primitive, the only way is literal notation. The specification explicitly makes the exception for the Array constructor getting one argument and the argument being a primitive number. Doing new Number(8)+0 is being evaluated before the constructor is invoked, resulting in exactly the same as new Array(8).

    Arrays are upper bound to (2^32)-1.

    If you initialize an array with the constructor and a single number parameter, the elements don’t actually get added. Instead, the length is simply set to the number and the array is considered “sparse”. You can test the properties to be non-existent by checking `if (“1″ in array)…`.

    – peter

    • Hi Peter, thanks for the correction with Number coercion getting evaluated before Array construction. Corrected that. Don’t know what I was thinking…

      Re. Arrays are upper bound to (2^32)-1….yeah I know but I deliberately didn’t mention the upper limit (just said it was very big) because no one should be creating arrays of that size :-)

      Not sure I understood your last point. The following does not return a sparse array

      var a = new Array(new Number(8));
      a.length; //1
      a[0] //8{}
      • Yeah I think I didn’t read it correctly. Or I can’t find it. I thought you meant that `new Array(5)` would create an array with 5 properties and initialize them as “undefined”. Sorry :)

  2. Good overview, Angus.

    A pair of corrections:

    to predefine an array with one primitive number using the new Constructor syntax is to create a first class object and then do something to coerce it back to a primitive

    [js]var b = new Array(8); //Wrong![/js]
    [js]var b = new Array(new Number(8) + 0); //Correct, but very clunky[/js]

    The end result is the same — an array of length 8 is created. Also, the phrase “first class object” here is not essential.

    element indexes 3-19 automatically created with value initialized to undefined

    Modification of the length property does not create elements. They are undefined just because of common [[Get]] algorithm — http://bit.ly/amf4io.

    [js]var a = [];
    a[1] = 10;

    alert(a.length); // 2

    alert(a[0]); // undefined, non-existing, by [[Get]]
    alert(“0″ in a); // false

    a[0] = undefined;
    alert(a[0]); // undefined, existing
    alert(“0″ in a); // true

    a.length = 3;

    // the same
    alert(a[2]); // undefined, non-existing, by [[Get]]
    alert(“2″ in a); // false[/js]

    you can delete an element but this just sets its value to undefined

    The same, delete just removes a property of an object, it does not set undefined value.

    [js]
    var a = [1, 2, 3];
    delete a[2];
    alert(“1″ in a); // false, no such index
    [/js]

    is an inappropriate usage of the array object. No array members are added (array.length is 0)

    It’s not about some `length` invariant that JS objects (including array objects) are not `associative arrays` or `hash tables`. The most important reason is that there is no semantic difference between subsciption and dot property accessor notations (for details — http://bit.ly/cA3bwY).

    Is there any advantage to using the new Array syntax?

    Some implementations (notably, WebKit or V8) can make optimization by allocating memory for array elements. But it’s not about spec, it’s about implementation. By spec, it’s just set the `length` property.

    //create a new array with 8 undefined members
    var a = new Array(8);

    This also does not create any element, but just sets the `length` invariant.

    [js]var a = new Array(8);
    alert(“0″ in a); // false[/js]

    Dmitry.

    • Thanks Dmitry

      >>new Array(new Number(8) + 0);

      Was having a crazy moment when I wrote that. :-)

      Very good points about what happens when length extended and delete invoked. I was expressing it form point of view of array observer – but yes this is a better explanation

      Re. “associative arrays”. I mentioned length = 0 was to point out this is not an Array at all. Arrays have length, this does not. Of course I get the dot notation and subscript being semantically identical property accessors- I think I mentioned it earlier in the article. Sorry if it did not come across.

      Re. Array constructor optimizations
      Does anyone really use them for this purpose?

      thanks
      Angus

  3. Another option to determine if an object is an array:

    function isArray(o){ return o.constructor==Array }

    • This works most of the time, but will screw up when working cross-frame or cross-document. (Eg. the Array object from an iframe is a different object than the Array object from your parent document)

  4. Pingback: Tweets that mention Understanding JavaScript Arrays « JavaScript, JavaScript -- Topsy.com

  5. On the beginning you write that it’s impossible to access array element by dot notation (what is true as far as i know), and then you try to do this in one of the examples:

    var a = [3,4,1];
    a.length; //3
    a[20] = 2;
    a.length; //21
    //element indexes 3-19 automatically created with value initialized to undefined
    a.18; //undefined

  6. Very nice write up! Thanks for sharing!

    “How do I know if my object is of type Array?”

    Can we use the below function to find out if the object is an Array or not? It works!

    function isArray(o){
        return o.constructor === Array;
    }
    

    Cheers,
    Abhinay Omkar

  7. Pingback: Elsewhere, on July 24th - Once a nomad, always a nomad

  8. Your definitions of reduce and reduceRight are identical. The latter should act right-to-left, of course.

    Now then, is there a way to replace every element of the w3 school’s description of JS arrays with your version?

  9. Pingback: Entendendo Arrays no JavaScript :: Top Tutoriais

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s