JavaScript Arrays - tips, tricks and examples
Open Source Your Knowledge, Become a Contributor
Technology knowledge has to be shared and made accessible for free. Join the movement.
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.
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.
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 arraypop()
--removes the last element --returns the element that was deleted
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 itempush(v)
– see above
Reverse FiFO
unshift()
– adds one or more elements to the beginning of an array and returns the new length of the arraypop()
– see above
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.
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.
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 itemfilter()
– returns an array of all items for which the function returns trueforEach()
– 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 arraysome()
– returns true if the function returns true for at least one of the items
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 -.
Final words
Thank you for your time!
If you had found this helpful, please subscribe to my website, WeeklyWebWisdom, for more articles like this!