JavaScript Arrays: The All-in-One Data Structure
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:
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:
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:
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 []
):
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:
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:
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:
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:
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:
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
:
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:
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:
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:
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:
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:
// 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:
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
whena
andb
are considered equal< 0
whena < b
> 0
whena > 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:
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:
// 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:
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:
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:
// 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:
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:
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:
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:
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:
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:
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.
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.
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:
// (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
:
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:
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:
[safer]: See http://bit.ly/javascriptmancy-is-this-an-array for more info on why instanceof is not a safe way to determine that an object is an array.
console.log('Shop is an array: ' + (shop instanceof Array))
With Array.isArray
it’s more straightforward:
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 []
:
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:
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].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:
// [].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:
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:
// 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:
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.
Have a great week ahead!! :)
More Articles In This Series
- Chapter 0: The Basic Ingredients of JavaScriptMancy
- Chapter 1: The Many a One JavaScript Quirks
- Functions
- Object Oriented Programming
- Basics
- ES6
- OOP
- Introduction to OOP in JavaScript for C# Developers
- Summoning Fundamentals: Introduction to OOP In JavaScript – Encapsulation
- Summoning Fundamentals: Introduction to OOP In JavaScript – Inheritance
- Summoning Fundamentals: Introduction to OOP In JavaScript – Polymorphism
- White Tower Summoning: Mimicking C# Classical Inheritance in Javascript
- White Tower Summoning Enhanced: The Marvels of ES6 Classes
- Black Tower Summoning: Object Composition with Mixins
- Safer JavaScript Composition With Traits and Traits.js
- JavaScript Ultra Flexible Object Oriented Programming with Stamps
- Object Oriented JavaScript for C# Programmers
- Tooling
- Data Structures
- Functional Programming
- And more stuff to be shaped out:
- Functional Programming with JavaScript
- Modules
- Asynchronous Programming
- The JavaScript Ecosystem
Written by Jaime González García , dad, husband, software engineer, ux designer, amateur pixel artist, tinkerer and master of the arcane arts. You can also find him on Twitter jabbering about random stuff.