JavaScript Arrays - tips, tricks and examples

WeeklyWebWisdom
3,310 views

Open Source Your Knowledge, Become a Contributor

Technology knowledge has to be shared and made accessible for free. Join the movement.

Create Content

Arrays are a a powerful and comprehensive tool of Javascript. They are very intuitive to use and you can do almost everything with it. However, there are important differences between arrays in Javascript and other mainstream languages. Knowing them will help you unleash their true potential.

In this article we will go through the important aspects of arrays that are imperative to every JS developer and we will see the variety of usages they have.


Arrays are just regular objects

In Javascript, there are only 6 data types defined – the primitives (boolean, number, string, null, undefined) and object (the only reference type). Arrays do not belong to this list because they are objects as well. This is a common confusion among developers who assume that arrays are a special data type in Javascript.

The items in the array are nothing more than properties of that objects. You can use any name as property for an objects, including numbers or even empty string. However, if the property name is not a valid identifier (doesn’t start with a letter), you can only access it through obj[property_name] and not obj.property_name.

So nothing stop us from taking an object and declaring properties like 0, 1, 2 and so on and accessing through obj[0], obj[1] and so on. You don’t need a special data type for doing that in Javascript.

1
2
3
4
5
6
7
8
9
// just your average object
var obj = {
0: 1,
1: 2,
'cat': 'meaow',
'': 'empty string'
};
console.log(obj[0], obj[1], obj['cat'], obj['']);
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

So you may ask: What is special about array object then?

An array has, alongside its elements, a special property named length and a generous collection of methods for manipulating the elements. They represent the real power of arrays. You can search, sort, delete, insert and even simulate behavior of other collection data types, like queue and stack.

Remember:

  • arrays are normal objects and their elements are properties with names 0, 1, .. etc.
  • arrays also have a special properties lenght and many function that can manipulate the elements

Declaring an array

Javascript’s elegance owes a lot to its concise literal syntax for the most common building blocks: object, functions and array. A literal is the best way to express an array:

var arr = [1, 2, 3, 4];

There is an alternative to that, the Array constructor:

var arr = new Array(1, 2, 3, 4);

Setting aside the aesthetics, there some subtle things you have to care about when using the array constructor. Since Array is a global variable, it can be modified somewhere else in the script so it may not behave as you expected.

Array = String; 
var arr = new Array(1, 2, 3, 4); // "1" 

One other issue is that if the Array constructor gets only one numerical argument, it will create a list with no elements but with length equal to that argument. So ['John'] is identical to new Array('John') but [10] it’s not the same thing to new Array(10).

It is possible to omit the new operator when using the Array constructor. It has the same result so new Array('John') or simply Array('John') are doing the same thing.

Remember:

  • use array literals instead of the Array constructor
  • the Array constructor behaves differently if its only argument is a number

Detecting an array

Truly determining whether a given object is an array has been one of the classic problems in Javascript.

When dealing with a single page, a single global scope and therefore a single Array global object things are easy:

if (value instanceof Array) {
// do some array stuff 
}

The problem with instanceof is that it assumes a single global execution context. Having multiple frames means multiple contexts and more than one Array global object. ECMAScript 5 come with the solution by introducing the method Array.isArray(value). It’s not affected by the number of global scopes. If you support ECMAScript 5 in your project, this is the best solution.


The length property

What is important to know about length is that when you access it, it simply returns the value of the biggest index + 1. This may seem odd, but the big advantage is that it’s very efficient. You won’t have to worry about performance when using it, because the elements are not counted at all. Whenever you add a new element, the value of length will be updated.

Also, you can manually set the value of it. You can truncate the array be setting a smaller length. A convenient way to clear an array is to set the length to 0.

Remember:

  • length simply returns the biggest index of the array + 1 –
  • you can clear the array by setting the length to 0

Since length simply returns the biggest index + 1, it’s value becomes irrelevant if you add a bigger index to the array.

1
2
3
4
var arr = [1, 2, 3, 4];
console.log(arr.length); // 4
arr[20] = 2;
console.log(arr.length); // 21 - even though there are no elements between index 5 and 19
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

It is a bad practice to do that. This is one of the downfalls of the array being so versatile. A bad code can cause ugly side effects.

Remember:

Don’t change the properties of an array like a regular object. Always rely on the methods provided to operate on its elements.


Array methods

It’s time to finally see them in action!

Any array object has access to a set of methods trough Array.prototype. Since we are talking about a global object, these methods can be altered. It is strongly advised against doing that. Modifying the Array.prototype will affect all the arrays in that global context.

However, I often encounter situations where the prototype scope was augmented by adding other helper functions to Array.prototype. For example, by default there is no method to return the first element of the array (without deleting it). You can add your own custom method:

var arr = [2, 2, 3, 4]

Array.prototype.first = function() {
return this[0];
}

console.log(arr.first()); // prints 1

And now every array in our global scope has access to the first() method. This may be convenient in some situations, but is generally a bad idea. If you modify a prototype, there is always the possibility to clash with other code from your program.

I compile below a list of methods that I consider important and worth to be remembered. It is a good practice to walk through all of them and practice in parallel.

Of course, nobody knows by heard all the details about them, but once you understand the behavior, you know where to search when you need it.


LIFO behavior (stack)

LIFO stands for Last-In-First-Out and represents a stack. Imagine a pile of plates. The last plate you added is the one on top of the pile and it's the only one you can take back. In a stack, there are 2 possible operation: add on top of the stack and remove the last element added from the stack

Functions that allow you to use the array as a stack:

  • push(v) -- adds an element v at the end of the array (at index length) --returns the new length of the array
  • pop() --removes the last element --returns the element that was deleted
1
2
3
4
5
6
7
var arr = [true, 2, 3, "4"];
arr.push(5);
console.log(arr); // [true, 2, 3, "4", 5]
last_element = arr.pop();
console.log(arr); // [true, 2, 3, "4"]
console.log(last_element); // 5
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

FIFO behavior (queue)

FiFo stands for First-In-First-Out. Imagine a queue of people standing in line at the store. The person that came first will be the one served. The last person that join the queue will have to wait for all other people to leave before he will be served. A queue in computer programming is similar: the add operation puts an element at the end of the queue and remove operation deletes elements from the start of the queue

  • shift() – returns the first item of the list and deletes the item
  • push(v) – see above
1
2
3
4
5
6
var arr = [true, 2, 3, "4"];
first_item = arr.shift();
console.log(arr); // [2, 3, "4"]
console.log(first_item); // true
arr.push(first_item);
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Reverse FiFO

  • unshift() – adds one or more elements to the beginning of an array and returns the new length of the array
  • pop() – see above
1
2
3
4
var arr = [true, 2, 3, "4"];
new_length = arr.unshift('new_elem');
console.log(arr); // ["new_elem", true, 2, 3, "4"]
console.log(new_length); // 5
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Reordering methods

  • reverse()

The function creates a duplicate of the original array and reverse the order of the elements. Again, the original array will not be affected.

  • sort()

Sorts the elements by string comparison. This may be a source of confusion when we try to sort a list of numbers and get an unexpected result. The function, by default, converts the elements to strings (using String()). So an array like this [4, 3, 1, 10] will be sorted [1, 10, 3, 4] because the string '10' is bigger than '3'. Of course, string comparison is not always the desired case. The function takes an optional parameter, a comparison function, which will replace the default string comparison. The function takes 2 parameters as arguments - element1 and element2 - and should return 0 if they are equal, a positive value if element1 is bigger and a negative value otherwise. The easiest way to make numeric comparison is to have a function which returns element1 - element2. The result will be a reference to the new sorted array.

1
2
3
4
5
6
7
8
9
10
var arr = [true, 2, 3, "4"];
new_array = arr.reverse()
console.log(new_array); // ["4", 3, 2, true]
new_array = arr.sort();
console.log(new_array); // [2, 3, "4", true]
var arr = [1, 2, 3, 4, 10];
console.log(arr.sort()); // [1, 10, 2, 3, 4]
console.log(arr.sort((el1, el2) => el1 - el2)); // [1, 2, 3, 4, 10]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Manipulation methods

  • concat()

Creates a new array based on the elements in the current array. It's important to know that the original array is not altered. The operation is made on the copy of that array. concat() accepts any number of arguments which will be added at the end of the array. Using it without arguments is a elegent way to clone the initial array.

  • slice()

Similar to concat(), slice creates a copy of the array and extracts a subsequence of it. Used with no parameters, it returns a copy of the original. With one argument - index -, returns all elements from that index to the end. Additionally, it can be used with 2 arguments - start_index and end_index, in which case it will return all elements in that interval (excluding the one from last_index).

The function can be used with a negative value as parameter. In this case, the start index will be counted from the end of the array - start_index = array.length - param

  • splice()

Splice() has very similar use cases like concat() and splice(), the only difference is that the operation is made on the original array.

It's a destructive method, you won't be able to revert it, so be careful to that when using it.

It can take more arguments splice(start_index, number_of_deleted_items, element1, element2, .... elementN). In this case, starting from start_index, it will delete the number of items specified on the second argument. The elements element1, element2..elementN will be added, in this order to the array. Basically, you replace a number of items with the elements you provide.

The function returns an array with the deleted items.

You can adapt the behavior as you wish. For example splice(start_index, number_of_deleted_items) for deleting elements or splice(start_index, 0, item_to_insert) for inserting without deleting.

1
2
3
4
5
6
7
8
9
10
var arr = [true, 2, 3, "4"];
new_array = arr.concat(5, 6, 7)
console.log(new_array); // [true, 2, 3, "4", 5, 6, 7]
new_array = arr.slice(0, 2);
console.log(new_array); // [true, 2]
console.log(arr); // arr not changed - [true, 2, 3, "4"]
arr.splice(0, 1, 1); // I replace the first item with 1
console.log(arr); // [1, 2, 3, "4"]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Iterative methods

Iterative methods are very versatile and used in many scenarios. You will most likely encounter them even when working with the latest frameworks as well (angular, react, vue).

You will provide a function which will be applied on every element on that array. The function will receive the array element as parameter and perform the operation. Optionally, the function takes other 2 arguments - index with the index of the elemnt in the list and array - a reference to the original array. If you are using them, you have to be careful to avoid modifying the array argument since this will affect the original array and cause unpleasant side effects.

Alongside the function, you can also provide a scope object - a reference to an object which will serve as this inside the function.

  • every() – returns true if function returns true on every item
  • filter() – returns an array of all items for which the function returns true
  • forEach() – no return value (just run the function on every element in the list)
  • map() – returns a new list with the result of each item in an array
  • some() – returns true if the function returns true for at least one of the items
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var every_result_is_smaller_than_9 = numbers.every(item => item < 9);
// this is equivalent with
var every_result_is_smaller_than_9 = numbers.every(function(item) { return item < 9; });
console.log(every_result_is_smaller_than_9); // false
var some_resulst_are_smaller_than_9 = numbers.some(item => item < 9);
console.log(some_resulst_are_smaller_than_9); // true
var odd_numbers = numbers.filter(item => item % 2 == 1);
console.log(odd_numbers); // [1, 3, 5, 7, 9]
numbers.forEach(item => console.log("works"));
var numbers_plus_one = numbers.map(item => item + 1);
console.log(numbers_plus_one);
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Note: The arrow function is a great fit for this case because it's making the code more readable. Nothing stops you from using a regular function instead.


Reduction Methods

As the name suggested, a reduction function reduce an array to a single result.

For example, let's imagine a list of numeric values. If I want to know the sum of all elements, I will take a variable sum which is initally 0 and iterate over the array. Each element will be added to my variable and, at the end of iteration, the result will be sum.

This is a perfect example of a reduce operation. I'm appling an operation to all items, progressively building the result. At each interation, I will have a partial result - the sum of elements I added so far - and the value of the element.

Reduce function are mostly used for calculating sums, concatenating values and all operation that can be applied to multiple values in order to get one result. The 1st argument of the reducer will be the function which will be called for each item. The function will have 2 parameter myFun(partial_result, current_item) and must return the partial result after dealing with the current item. Optionally, like in the case of iterative methods, it can take other 2 arguments - index and array -.

The second argument - initial_value - represent the partial result we will have at the first call. This would be equivalent with initializing our sum with 0 before the start of the iteration. At the first call of the function, current_item will be the first item in the list and partial_result will be initial value. If the second argument is not provided, the reduction will start at the second item in the list. In the first call, current_item will be the second item and partial_result will be the first item in the list. This is very convenient for some cases, including in our example.

Javascript provides 2 reducer function: reduce() and reduceRight() - which iterates the elements starting from the end of the array -.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var total = numbers.reduce(function(sum, value) {
return sum + value;
}, 0);
console.log(total);
// here I don't provide the seconds argument (initial value) ->
// at first call, sum = numbers[0] and value = numbers[1] so the result is the same
var total = numbers.reduce(function(sum, value) {
return sum + value;
});
console.log(total);
var concatenate = numbers.reduceRight((str, value) => str = str + value, '');
console.log(concatenate); // '987654321'
// we start from '' add concatenate each value, starting from the right
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Final words

Thank you for your time!

If you had found this helpful, please subscribe to my website, WeeklyWebWisdom, for more articles like this!

Open Source Your Knowledge: become a Contributor and help others learn. Create New Content