JavaScript Arrays: The All-in-One Data Structure

| Comments

The Mastering the Arcane Art of JavaScript-mancy series are my humble attempt at bringing my love for JavaScript to all other C# developers that haven’t yet discovered how awesome this language and its whole ecosystem are. These articles are excerpts of the super duper awesome JavaScript-Mancy book a compendium of all things JavaScript for C# developers.

For a very long time (up until the year 2015 and the advent of ES6) the only data structure available in JavaScript was the array. This was not a big problem for the hordes of JavaScript developers because JavaScript’s array is an array, a list, a queue, a stack and in addition provides similar functionality to LINQ. Sounds interesting? Let’s have a look.

JavaScript’s Array

You can experiment with all examples in this article directly within this jsBin.

The easiest way to create an array in JavaScript is to use the array literal:

1
2
3
var rightPocket = [];
console.log('This is what I have in my right pocket: ' + rightPocket);
// => This is what I have in my right pocket: 

Although you can use the Array constructor as well:

1
2
3
var leftPocket = Array();
console.log('And this is what I have in my left pocket: ' + leftPocket);
// => And this is what I have in my left pocket: 

Which unfortunately is a little bit inconsistent. For instance, you can use the Array constructor with one single argument to create an array of an arbitrary size:

1
2
3
4
console.log(Array(1));
// => [undefined]
console.log(Array(3));
// => [undefined, undefined, undefined]

Or you can use it with more than one argument to create an array that will contain these arguments that you pass in (just like []):

1
2
console.log(Array(1, 2, 3));
// => [1, 2, 3]

As you would expect from any array worth its salt, you can set items in the array by index:

1
2
3
4
5
6
7
rightPocket[0] = 'bearded axe';
leftPocket[0] = '10 gold coins';

console.log('right pocket: ' + rightPocket);
// => right pocket: bearded axe
console.log('left pocket: ' + leftPocket);
// => left pocket: 10 gold coins

And you can also retrieve these items by indexing the array:

1
2
3
4
console.log('I have a ' + rightPocket[0] + ' bearded axe in my right pocket. ' +
'I am maniac... and I have to patent this pants...');
// => I have a bearded axe bearded axe in my right pocket. 
//    I am maniac... and I have to patent this pants...

Arrays have a dynamic size and they grow as you add new elements to them. You can access the size of an array using its length property:

1
2
3
4
5
console.log('The size of my right pocket is: ' + rightPocket.length);
// => The size of my right pocket is: 1
rightPocket[1] = "orb of power";
console.log('And now it is: ' + rightPocket.length);
// => And now it is 2

Specially interesting is the fact that JavaScript allows you to have elements of disparate types within the same array:

1
2
3
4
var leatherBag = ['20 gold coins',
  {name: 'wand of invisibility', charges: 1, toString(){return this.name;}}];
console.log('You examine your leather bag and find: ' + leatherBag);
// => You examine your leather bag and find: 20 gold coins,wand of invisibility

An Extremely Flexible Data Structure

The Array was the sole data structure available in JavaScript for a long time and, as such, it grew to provide a lot of functionality to cover most of the use cases that you usually run into when writing JavaScript applications. It is somewhat of an all-purpose collection.

For instance, it can work as a stack (LIFO – Last In First Out) if you use the methods push and pop. These methods add and remove elements from the end of the array respectively:

1
2
3
rightPocket.push('chewing gum');
console.log('You get the ' + right.Pocket.pop());
// => You get the chewing gum

It can work as a queue (FIFO – First In First Out) if you use the method shift to extract an item from the beginning of the array and combine it with push:

1
2
3
4
5
leftPocket.push('cheese sandwich');
console.log('You pay the cheese sandwich with ' +
    leftPocket.shift() + '. That was a pricy sandwich...');
// => You pay the cheese sandwich with 10 gold coins. 
//    That was a pricy sandwich...

You can also insert items in the beginning of the array by using the unshift method:

1
2
3
leftPocket.unshift('beautiful stone');
console.log('You examine the ' + leftPocket[0] + ' in wonder.');
// => You examine the beautiful stone in wonder.

Additionally, both push and unshift let you add multiple items at once:

1
2
3
4
5
6
leatherBag.push('dried meat', 'white feather');
leatherBag.unshift('1 copper coin', 'skeleton skull');
console.log('You look inside your leather bag and find: ' + leatherBag);
// => You look inside your leather bag and find: 
//    1 copper coin,skeleton skull,20 gold coins,
//    wand of invisibility,dried meat,white feather

Another useful method that lets you remove items from any arbitrary position of an array is the splice method. It has many use cases:

1
2
3
4
5
6
7
8
9
10
11
12
var firstItem = leatherBag.splice(/* start */ 0, /* numberOfItemsToRemove */ 1);
console.log('extracted first item => ' + firstItem);
// => extracted first item => 1 copper coin

// you can use negative indexes to start from the end of the array
var lastItem = leatherBag.splice(-1, 1);
console.log('extracted last item => ' + lastItem);
// => extracted last item => white feather

var someRandomItemsInTheMiddle = leatherBag.splice(1, 2);
console.log('extracted items in the middle => ' + someRandomItemsInTheMiddle);
// => extracted items in the middle => 20 gold coins,wand of invisibility

splice can even insert items at a given point:

1
2
3
4
5
6
7
8
console.log(rightPocket);
// => ["bearded axe", "orb of power"]

// let's add a couple of items in between the axe and the orb
// splice(startIndex, numberOfItemsToRemove, item1, item2, etc...)
rightPocket.splice(1, 0, "candlestick", "yerky");
console.log(rightPocket);
// => ["bearded axe", "candlestick", "yerky", "orb of power"]

or remove items and insert items at once:

1
2
3
4
// splice(startIndex, numberOfItemsToRemove, item1, item2, etc...)
let candle = rightPocket.splice(1, 1, "secret message", "wax");
console.log(rightPocket);
// => ["bearded axe", "secret message", "wax", "yerky", "orb of power"]

Sorting Arrays

Arrays offer a couple of methods that let you sort the elements they contain. The first one that you need to consider is the sort method which takes a compare function as argument. Let’s sort our potions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function Potion(name, quantity){
  return {
    name,
    quantity,
    toString(){return `(${this.quantity}) ${this.name}`;}
  };
}

var potionsCase = [
  Potion('potion of firebreathing', 2),
  Potion('potion of vigor', 1),
  Potion('potion of major healing', 3),
  Potion('potion of cure poison', 1)
];
// the compare function f(a,b) should return:
// < 0 if a < b
// 0 if a === b
// > 0 if a > b
potionsCase.sort((p1,p2) => p1.quantity - p2.quantity);
console.log("You examine your potion case closely... " + potionsCase);
// => You examine your potion case closely... 
//     (1) potion of cure poison,
//     (1) potion of vigor,
//     (2) potion of firebreathing
//     (3) potion of major healing,

And it looks like you need to buy some more of that curing poison because you never know what may be waiting to bite you behind that next corner. The compare function compare(a,b) is expect to return:

  • 0 when a and b are considered equal
  • < 0 when a < b
  • > 0 when a > b

Another fact that is important to remember is that the sort method does in-place sorting so your original array will reflect the changes after being sorted.

The second array method related to sorting is the reverse method. This method reverses the position of all items within the array and does so in-place:

1
2
3
4
5
6
console.log("Let's' see what I can sell... " + potionsCase.reverse());
// => Let's' see what I can sell... 
//    (3) potion of major healing,
//    (2) potion of firebreathing,
//    (1) potion of cure poison,
//    (1) potion of vigor

Safe Array Methods

All these methods that we have seen up until now mutate the array itself, that is, using them will change the inner contents of the array. Let’s look at some safe methods now, methods that don’t change the original array.

The concat method lets you concatenate two arrays together and returns the resulting array:

1
2
3
4
5
// concatenate arrays with concat
var superPocket = rightPocket.concat(leftPocket);
console.log(superPocket);
// => ["bearded axe", "secret message", "wax", "yerky", 
//    "orb of power", "beautiful stone", "cheese sandwich"]

The join method allows you to join the elements of an array to form a string using an arbitrary separator of your choice:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function beautifyPocket(pocket){
  return pocket.join('\n=============\n');
}
console.log(`You examine your inventory: \n
${beautifyPocket(rightPocket)}`);
// => You examine your inventory: 
//    
//    bearded axe
//    =============
//    secret message
//    =============
//    wax
//    =============
//    yerky
//    =============
//    orb of power

The indexOf method returns the position of an item within an array:

1
2
var indexOfBeardedAxe = rightPocket.indexOf('bearded axe');
console.log('The bearded axe is at position: ' + indexOfBeardedAxe);

It is often used to find out whether or not an array contains a given item like this:

1
2
3
4
5
// indexOf returns -1 when it can't find an item
if (rightPocket.indexOf('red stone') === -1)
{
    console.log("You don't have a precious red stone in your pocket");
}

indexOf returns the first ocurrence of an item in an array, alternatively you can use lastIndexOf to find the last ocurrence of an item.

The last safe array method is slice which is a non-destructive alternative to splice. This being JavaScript we couldn’t have a similar signature. Instead of working with the start index and the number of items to remove, the slice method expects the start and end of the subarray to extract:

1
2
3
4
5
6
7
8
9
10
11
console.log('leather bag has ' + leatherBag.length + ' items: ' + leatherBag);
// => leather bag has 2 items: skeleton skull,dried meat

// let's be god and reproduce the dried meat
console.log(leatherBag.slice(/* start */ 1, /* end */ 3));
// => ['dried meat']
// Note how slice extracts up to but not including the end

// we still have two items in the original array
console.log('leather bag has ' + leatherBag.length + ' items: ' + leatherBag);
// => leather bag has 2 items: skeleton skull,dried meat

slice also supports negative indices which represent starting counting from the end of the array. The end parameters is also optional, so we can extract a new array containing only the last item from the original array pretty easily:

1
2
3
var lastItem = leatherBag.slice(-1);
console.log(lastItem);
// => ["dried meat"]

Iterating an array

Prior to ES6, JavaScript offered two ways in which to iterate over the elements of an array: the for/in loop and the Array.prototype.forEach method.

The for/in loop is a JavaScript construct that lets you iterate over the properties of any object, in the case of an array, these properties are the indices of the array:

1
2
3
4
5
6
7
console.log('You examine your inventory: ');
for(var index in leatherBag){
  console.log(leatherBag[index]);
}
// => You examine your inventory: 
//    skeleton skull"
//    dried meat"

The forEach method offers a better developer experience to iterating as it gives you each item of the array directly:

1
2
3
4
5
6
7
console.log('You examine your inventory.... closer: ')
leatherBag.forEach(function(item) {
  console.log('You examine ' + item + ' closely');
});
// => You examine your inventory.... closer: 
//    You examine skeleton skull closely
//    You examine dried meat closely

And additionally gives you access to each index and the array itself:

1
2
3
4
5
6
7
console.log('You examine your inventory.... veeery closely: ')
leatherBag.forEach(function(item, index, array) {
  console.log('You examine ' + item + ' closely (' + (index+1) + '/' + array.length + ')');
});
// => You examine your inventory.... veeery closely:
//    You examine skeleton skull closely (1/2)
//    You examine dried meat closely (2/2)

ES6 generalizes the concept of iterability in JavaScript through the addition of the iterator protocol and the for/of loop:

1
2
3
4
5
6
7
console.log('You look at the stuff in your bag:');
for(let item of leatherBag){
  console.log(item);
}
// => You look at the stuff in your bag:
//    skeleton skull
//    dried meat

You can find a ton more information about the iterator protocol and the for/of loop in this article.

JavaScript Arrays and LINQ

If you thought that everything you’ve seen thus far was everything there was about JavaScript arrays you’re in for a treat, because Wait! There is more!.

One of my favorites features of JavaScript array is that it comes with a set of methods that are very similar to .NET LINQ, yes, you read it, isn’t that absolutely awesome? I devoted a whole article to the Array’s LINQ-like methods in this article but here’s a small appetizer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var shop = [
  {name: 'sword of truth', type: 'sword', damage: 60, price: 1000},
  {name: 'shield of stamina', type: 'shield', defense: 50, price: 500,
   modifiers: [{value: 2, characteristic: 'stamina'}]},
  {name: 'minor potion of healing', type: 'potion', price: 1,
   effects: [{value: 10, characteristic: 'hitPoints'}]},
  {name: 'grand potion of healing', type: 'potion', price: 7,
   effects: [{value: 50, characteristic: 'hitPoints'}]}
];

console.log('The shopkeeper looks at you greedily and tells you:');
console.log('*These are the potions we have today sir...' +
           'they are the best in the kingdowm!*');
var potions = shop
      .filter(item => item.type === 'potion')
      .map(potion => potion.name);
for(let potion of potions){
    console.log(potion);
}
// => The shopkeeper looks at you greedily and tells you:
//    *These are the potions we have today sir...they are the best in the kingdowm!*
//    minor potion of healing
//    grand potion of healing

I used arrow functions in this example to give you a feeling of familiarity between the Array.prototype LINQ-like methods and LINQ, but you must know that most of these methods are available in ES5 and work just as well with normal function expressions.

1
2
3
4
5
6
7
8
var totalPrice = shop
      .map(function(item){return item.price;})
      .reduce(function(total, itemPrice){
        return total + itemPrice;
      }, /* initialTotal */ 0);
console.log('The total price of the items is ' + totalPrice +
            ' silvers');
// => The total price of the items is 1508 silvers

Other ES6 and ES7 Features

In addition to formalizing the concept of iteration in Arrays, ES6 brings several new helpful methods that will make operating on Arrays and Array-likes easier.

The Array.from method let’s you create an array from any array-like and iterable object. It is ES6’s solution to the commonplace practice of using Array.prototype.slice in ES5 to convert array-likes into proper arrays:

1
2
3
4
5
6
7
// (in this case it'd better to use the rest syntax ...items)
function sortItems(){
  var items = Array.from(arguments);
  return items.sort();
}
console.log(sortItems('mandragora', 'amber', "elf's tongue"));
// => ["amber", "elf's tongue", "mandragora"]

We will take a look at iterable objects later in this section, but any object that can be iterated over can be converted into an array using Array.from. For instance, a Map:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var library = new Map();
library.set('horror', ['It', 'The thing', 'horrors of Swarland']);
library.set('love', ['Romance and Betrayal', 'Beauty I']);
library.set('history', ['The fall of the Kraagg Empire']);

console.log('Welcome to the library of Anriva!' +
           ' These are our most valuable books');
Array.from(library)
     .forEach(keyValuePair => {
  console.log(keyValuePair);
});
// => ["horror", ["It", "The thing", "horrors of Swarland"]]
//    ["love", ["Romance and Betrayal", "Beauty I"]]
//    ["history", ["The fall of the Kraagg Empire"]]

Array.from also takes a second optional argument, a map function that just like LINQ’s Select let’s you transform each element within the source array into something else of your own choosing:

1
2
3
4
5
6
7
8
9
function sortItemsProperty(selector, ...args){
  var items = Array.from(args, selector);
  return items.sort();
}

console.log(sortItemsProperty(i => i.price,
  {name: 'mandragora', price: 2},
  {name: 'amber', price: 10}));
// => [10, 2]

The Array.isArray method provides a more convenient and safer way to check whether an object is an array or not. Prior to ES6 we used to use the following approach:

1
console.log('Shop is an array: ' + (shop instanceof Array));

With Array.isArray it’s more straightforward:

1
console.log('Shop is an array: ' + Array.isArray(shop));

The Array.of method lets you create an array from a variable number of arguments and is equivalent to []:

1
2
let ingredients = Array.of('bat wings', 'unicorn horn', 'sesame seeds');
// => ['bat wings', 'unicorn horn', 'sesame seeds']

Why would you want to use Array.of instead of [] then? There is a corner case application where Array.of is essential, when creating Array subclasses:

1
2
3
4
5
6
7
8
9
10
11
12
13
class ItemsArray extends Array{
  price(){
    return this.map(i => i.price).reduce((a, p) => a + p, 0);
  }
}
// how can you instantiate an array of ItemsArray in a consistent way
let itemsArray = ItemsArray.of(
  {name: 'bat wings', price: 10},
  {name: 'unicorn horn', price: 10000},
  {name: 'sesame seeds', price: 1}
)
console.log(`the price of all your wares is ${itemsArray.price()} golden coins`);
// => the price of all your wares is 10011 golden coins

Array.prototype.copyWithin() provides a way to copy items within the same array, that is, pick a portion of an array and copy it within the same array. Let’s illustrate it with an example:

1
2
3
4
5
6
7
8
[1, 2, 3, 4, 5].copyWithin(/* target index */ 0, /* start */ 3, /* end */ 4);
// copies the items between indexes 3 and 4 => the item 4
// into the index 0 of the array
// [4, 1, 3, 4, 5]

// if you leave the end out, it defaults to the length of the array
[1, 2, 3, 4, 5].copyWithin(/* target index */ 0, /* start */ 3);
// [4, 5, 3, 4, 5]

Array.prototype.fill() provides a convenient way to fill an existing array with a specific item:

1
2
3
4
// [].fill(item, start=0, end=this.length)
[1, 2, 3].fill(':)');               // [':)', ':)', ':)']
[1, 2, 3].fill(':)', 1);            // [1, ':)', ':)']
[1, 2, 3].fill(':)', 1, 2);         // [1, ':)', 3]

New Array methods in ES7

ES7, while being a very small incremental release of JavaScript, brings a very convenient way to check whether an item exists within an array, the Array.prototype.includes() method:

1
2
3
4
if (!rightPocket.includes('red stone'))
{
    console.log("You don't have a precious red stone in your pocket");
}

This provides a much better developer experience than the indexOf method that we saw previously in this article:

1
2
3
4
5
// indexOf returns -1 when it can't find an item
if (rightPocket.indexOf('red stone') === -1)
{
    console.log("You don't have a precious red stone in your pocket");
}

In addition to providing which item within the array you are looking for, you can specify an starting index for the search:

1
2
3
let herbs = ['sage', 'salvia', 'aloe vera'];
console.log('Is sage the last item in my herb poach?:', herbs.includes('sage', herbs.length);
// =>

Concluding

JavaScript’s array is an all-purpose collection, a extremely and versatile data structure that will cover most of you application needs. You can use it as a stack, a queue, a list, you can easily perform destructive and non-destructive operations on it. It also has support for LINQ-like functionality that will make working with collections of items a breeze.

Even though JavaScript’s array is awesome, there’s a couple of use cases that are best suited for other data structures: storing items by an arbitrary key and managing collections of unique items. And that’s what we will learn in the next two articles of these series since both Maps (like a Dictionary<T,T>) and Sets are two new data structures available from ES6 onwards.

Buy the Book!

Are you a C# Developer interested in learning JavaScript? Then take a look at the JavaScript-mancy book, a complete compendium of JavaScript for C# developers.

a JavaScriptmancy sample cover

Have a great Eastern! :)

More Articles in These Series

Comments