Declaration

JavaScript array can be created in two ways -

let arr = new Array();
let arr = [];

It is always recomended to use the second approach. Which is more convenitent.

let fruits = ["Apple", "Orange", "Plum"];

Accessing

You can access items using the index starting from 0.

let fruits = ["Apple", "Orange", "Plum"];

alert( fruits[0] ); // Apple
alert( fruits[1] ); // Orange
alert( fruits[2] ); // Plum

Replacing

If you assign an item to the index that already exists, the item will be overwritten.

fruits[2] = 'Pear'; // now ["Apple", "Orange", "Pear"]
fruits[3] = 'Lemon'; // now ["Apple", "Orange", "Pear", "Lemon"]

In the above example, the index 3 doesn't exist, so it will create a new item in the array.

If can assign an item to the any index you want -

let arr = ["Apple", "Orange", "Pear", "Lemon"];
arr[10] = "Guava"; // Okay

But in this process, the intermediate index will be treated as undefined. Infact, trying to access an element with the index that is out of array length is not an error. It simply returns undefined.

Never add an array item this way, unless you know what you are doing. Intermediate gap of index may cause unexpected result when you iterate over the array.

Trailing Comma

Just like Object, last array item may end with a trailing comma -

let arr = ["Apple", "Orange", "Pear", "Lemon", ];

Type of Elements

An array can contain any type of data as the item. It can store another array, object, number, string, function, Boolean etc.

// mix of values
let arr = [ 'Apple', { name: 'John' }, true, function() { alert('hello'); } ];

// get the object at index 1 and then show its name
alert( arr[1].name ); // John

// get the function at index 3 and run it
arr[3](); // hello

In real life development and business logic, we always store same kind of data model to the array collection. It means each item has the same signature of the of the others.

Properties and Methods

length

This returns the total number of item exists in the array.

let arr = ['Santanu', 'Neo', 'Blaster'];
console.log(arr.length); // Output : 3

The length property automatically updates when we modify the array. To be precise, it is actually not the count of values in the array, but the greatest numeric index plus one.

let fruits = [];
fruits[123] = "Apple";

alert( fruits.length ); // 124

Note that we usually don’t use arrays like that. Another interesting thing about the length property is that it’s writable. If we increase it manually, nothing interesting happens. But if we decrease it, the array is truncated.

let arr = [1, 2, 3, 4, 5];

arr.length = 2; // truncate to 2 elements
alert( arr ); // [1, 2]

arr.length = 5; // return length back
alert( arr[3] ); // undefined: the values do not return

array.push(item)

This method adds an item at the end of the array. This methods returns the new array.length after the insertion of new item.

let fruits = ['Apple', 'Banana', 'Guava'];
fruits.push('Pear');
log(fruits); // ['Apple', 'Banana', 'Guava', 'Pear']

The call fruits.push(...) is equal to fruits[fruits.length]. It can add multiple items at once -

fruits.push('Date', 'Coconut');
log(fruits); // ['Apple', 'Banana', 'Guava', 'Pear', 'Date', 'Coconut']

array.pop()

The pop() method removes the last element from an array. This method doesn't accept any argument.

let fruits = ['Apple', 'Banana', 'Guava'];
fruits.pop();
log(fruits); // ['Apple', 'Banana']

This method returns the value that was removed.

let fruits = ['Apple', 'Banana', 'Guava'];
log(fruits.pop()); // ['Apple', 'Banana']

array.shift()

This method removes an item from the beginning. It also shift rest of the item to the upwards so that they are indexed properly.

let fruits = ['Apple', 'Banana', 'Guava'];
fruits[0]; // Apple
fruits[1]; // Banana
fruits[2]; // Guava
fruits.shift(); // Removes "Apple"
fruits[0]; // Banana
fruits[1]; // Guava

This method returns the item that was removed from the beginning.

array.unshift(item)

This method adds an item at the beginning and reorder the index.

let fruits = ['Apple', 'Banana', 'Guava'];
fruits[0]; // Apple
fruits[1]; // Banana
fruits[2]; // Guava
fruits.unshift("Pear"); // Removes "Apple"
fruits[0]; // Pear
fruits[1]; // Apple
fruits[2]; // Banana
fruits[3]; // Guava

It returns the new array.lenght after adding item at the beginning. It can add multiple item at once -

let fruits = ['Apple', 'Banana', 'Guava'];
fruits.unshift('Date', 'Coconut'); // ['Apple', 'Banana', 'Guava', 'Date', 'Coconut']

delete

delete operator is used to delete an item. It returns Boolean specifies whether the deletion operation was successful or not.

let fruits = ['Apple', 'Banana', 'Guava'];
delete fruits[0];
log(fruits); // ['Banana', 'Guava'];

The advantage of delete operator is that you can delete an item from any index you want. And the disadvantage of using delete operator is that, it doesn't reorder the index. If you delete item from the index n, and array[n] will output undefined.

let fruits = ['Apple', 'Banana', 'Guava'];
delete fruits[1];
fruits[1]; // undefined

Also the length property is not updated. To efficiently delete item from the array consider using splice() or slice() methods.

alert(fruites.length); // 3

That’s natural, because delete obj.key removes a value by the key. It’s all it does. Fine for objects. But for arrays we usually want the rest of elements to shift and occupy the freed place. We expect to have a shorter array now.

toString

Arrays have their own implementation of toString method that returns a comma-separated list of elements.

let arr = [1, 2, 3];

alert( arr ); // 1,2,3
alert( String(arr) === '1,2,3' ); // true

alert( [] + 1 ); // "1"
alert( [1] + 1 ); // "11"
alert( [1,2] + 1 ); // "1,21"

So basically if you perform concatenation operation with an array, the array is automatically converted to the string first and then the concatenation happens.

array.splice()

The splice method can be used to remove element from the array.

var fruits = ["Banana", "Orange", "Apple", "Mango"];
// fruits.splice(index, numberOfitems);
fruits.splice(0,1); // Removes the first element.
fruits.splice(fruits.length-1,1); // Removes the last element.

The first argument is the index from which the removing will start. And the second argument specifies the number of item that will be remove from the array.

The splice() methods also accepts any number of additional argument and if they are present they will be added in the array at the index index.

var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.splice(1,2, 'Date', 'Coconut'); // ['Banana', 'Date', 'Coconut', 'Mango'];

The above example removes 2 items from the index and adds two item(additional arguments) at the index 1.

Removing item using splice() method has advantage over using delete operator. This method automatically reorder the index and update the length property of the array.

This method returns the removed elements -

let arr = ["I", "study", "JavaScript", "right", "now"];

// remove 2 first elements
let removed = arr.splice(0, 2);

alert( removed ); // "I", "study" <-- array of removed elements

The splice method is also able to insert the elements without any removals. For that we need to set second argument to 0:

let arr = ["I", "study", "JavaScript"];

// from index 2
// delete 0
// then insert "complex" and "language"
arr.splice(2, 0, "complex", "language");

alert( arr ); // "I", "study", "complex", "language", "JavaScript"

Here and in other array methods, negative indexes are allowed. They specify the position from the end of the array, like here:

let arr = [1, 2, 5];

// from index -1 (one step from the end)
// delete 0 elements,
// then insert 3 and 4
arr.splice(-1, 0, 3, 4);

alert( arr ); // 1,2,3,4,5

slice()

The method arr.slice is much simpler than similar-looking arr.splice. The syntax is -

arr.slice(start, end)

It returns a new array containing all items from index "start" to "end" (not including "end"). Both start and end can be negative, in that case position from array end is assumed.

It works like str.slice, but makes subarrays instead of substrings.

let str = "test";
let arr = ["t", "e", "s", "t"];

alert( str.slice(1, 3) ); // es
alert( arr.slice(1, 3) ); // e,s

alert( str.slice(-2) ); // st
alert( arr.slice(-2) ); // s,t

arr.concat()

This method concats multiple array into one array and returns it.

arr.concat(arg1, arg2...)

It accepts any number of arguments – either arrays or values. The result is a new array containing items from arr, then arg1, arg2 etc.

let arr = [1, 2];

// merge arr with [3,4]
alert( arr.concat([3, 4])); // 1,2,3,4

// merge arr with [3,4] and [5,6]
alert( arr.concat([3, 4], [5, 6])); // 1,2,3,4,5,6

// merge arr with [3,4], then add values 5 and 6
alert( arr.concat([3, 4], 5, 6)); // 1,2,3,4,5,6

Iteration

One of the common usage of array to iterate over the array items. We can do this by old school for statement way. Like this -

let arr = ["Apple", "Orange", "Pear"];

for (let i = 0; i < arr.length; i++) {
  alert( arr[i] );
}

That's fine. But there's special type of looping just for array for.. of

let fruits = ["Apple", "Orange", "Plum"];

// iterates over array elements
for (let fruit of fruits) {
  alert( fruit );
}

This way in the first iteration, the first item is copied to the fruit, and in the second iteration, the second item is copied and so on.

Technically, array is an object in JS. So you can use in operator insetead of of, which will be the same result in most cases. But using this is really a bad idea. There are lots of flaws and other disadvantage of using in operator with the for loop.

let arr = ["Apple", "Orange", "Pear"];

for (let key in arr) {
  alert( arr[key] ); // Apple, Orange, Pear
}

Remember that, of operator is for looping array and in operator for looping an object.

Array is actually an Object

An array is a special kind of object. The square brackets used to access a property arr[0] actually come from the object syntax. Numbers are used as keys. They extend objects providing special methods to work with ordered collections of data and also the length property. But at the core it’s still an object. Remember, there are only 7 basic types in JavaScript. Array is an object and thus behaves like an object. For instance, it is copied by reference:

let fruits = ["Banana"]

let arr = fruits; // copy by reference (two variables reference the same array)

alert( arr === fruits ); // true

arr.push("Pear"); // modify the array by reference

alert( fruits ); // Banana, Pear - 2 items now

…But what makes arrays really special is their internal representation. The engine tries to store its elements in the contiguous memory area, one after another, just as depicted on the illustrations in this chapter, and there are other optimizations as well, to make arrays work really fast. But they all break if we quit working with an array as with an “ordered collection” and start working with it as if it were a regular object. For instance, technically we can do this:

let fruits = []; // make an array

fruits[99999] = 5; // assign a property with the index far greater than its length

fruits.age = 25; // create a property with an arbitrary name

That’s possible, because arrays are objects at their base. We can add any properties to them. But the engine will see that we’re working with the array as with a regular object. Array-specific optimizations are not suited for such cases and will be turned off, their benefits disappear.

The ways to misuse an array:

Please think of arrays as special structures to work with the ordered data. They provide special methods for that. Arrays are carefully tuned inside JavaScript engines to work with contiguous ordered data, please use them this way. And if you need arbitrary keys, chances are high that you actually require a regular object {}.

Performance

Methods push/pop run fast, while shift/unshift are slow. Consider the following -

fruits.shift(); // take 1 element from the start

It’s not enough to take and remove the element with the number 0. Other elements need to be renumbered as well. The shift operation must do 3 things:

The more elements in the array, the more time to move them, more in-memory operations. The similar thing happens with unshift: to add an element to the beginning of the array, we need first to move existing elements to the right, increasing their indexes.

The pop method does not need to move anything, because other elements keep their indexes. That’s why it’s blazingly fast. The similar thing with the push method.

new Array()

There is one more syntax to create an array:

let arr = new Array("Apple", "Pear", "etc");

It’s rarely used, because square brackets [] are shorter. Also there’s a tricky feature with it. If new Array is called with a single argument which is a number, then it creates an array without items, but with the given length.

let arr = new Array(2); // will it create an array of [2] ?

alert( arr[0] ); // undefined! no elements.

alert( arr.length ); // length 2

In the code above, new Array(number) has all elements undefined. To evade such surprises, we usually use square brackets, unless we really know what we’re doing.

Iterate: forEach

The arr.forEach method allows to run a function for every element of the array.

arr.forEach(function(item, index, array) {
  // ... do something with item
});

For example -

["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => {
  alert(`${item} is at index ${index} in ${array}`);
});

Searching in array

These are methods to search for something in an array.

indexOf/lastIndexOf and includes

The methods arr.indexOf, arr.lastIndexOf and arr.includes have the same syntax and do essentially the same as their string counterparts, but operate on items instead of characters:

let arr = [1, 0, false];

alert( arr.indexOf(0) ); // 1
alert( arr.indexOf(false) ); // 2
alert( arr.indexOf(null) ); // -1

alert( arr.includes(1) ); // true

Note that the methods use === comparison. So, if we look for false, it finds exactly false and not the zero.

If we want to check for inclusion, and don’t want to know the exact index, then arr.includes is preferred.

Also, a very minor difference of includes is that it correctly handles NaN, unlike indexOf/lastIndexOf:

const arr = [NaN];
alert( arr.indexOf(NaN) ); // -1 (should be 0, but === equality doesn't work for NaN)
alert( arr.includes(NaN) );// true (correct)

find and findIndex

Imagine we have an array of objects. How do we find an object with the specific condition? Here the arr.find method comes in handy.

let result = arr.find(function(item, index, array) {
  // should return true if the item is what we are looking for
});

Here the find method accepts a function which must return Boolean value. The function is called repetitively for each element of the array:

If the argument method returns true, the search is stopped, the item is returned. If nothing found, undefined is returned.

For example, we have an array of users, each with the fields id and name. Let’s find the one with id == 1:

let users = [
  {id: 1, name: "John"},
  {id: 2, name: "Pete"},
  {id: 3, name: "Mary"}
];

let user = users.find(item => item.id == 1);

alert(user.name); // John

In real life arrays of objects is a common thing, so the find method is very useful. Note that in the example we provide to find the function item => item.id == 1 with one argument. Other arguments of this function are rarely used.

The arr.findIndex method is essentially the same, but it returns the index where the element was found instead of the element itself.

filter()

The find method looks for a single (first) element that makes the function return true. If there may be many, we can use arr.filter(fn). The syntax is roughly the same as find, but it returns an array of matching elements:

let results = arr.filter(function(item, index, array) {
  // should return true if the item passes the filter
});

For example -

let users = [
  {id: 1, name: "John"},
  {id: 2, name: "Pete"},
  {id: 3, name: "Mary"}
];

// returns array of the first two users
let someUsers = users.filter(item => item.id < 3);

alert(someUsers.length); // 2

Transform an array

This section is about the methods transforming or reordering the array.

map()

The arr.map method is one of the most useful and often used.

let result = arr.map(function(item, index, array) {
  // returns the new value instead of item
});

This method has a function as an argument. The returned value of that function is replaced by the array item.

It calls the function for each element of the array and returns the array of results. For instance, here we transform each element into its length:

let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length);
alert(lengths); // 5,7,6

Here is another example -

[1,2,3].map(function(item){return item+1;}); // [2, 3, 4]

It doesn't change the original array -

let a = [1, 2];
let b = a.map(function(item){return item+1;});
console.log(a); // [1, 2]
console.log(b); // [2, 3]

sort()

The method sorts the array in place.

['Banana', 'Apple', 'Grape', 'Strawberry', 'Guava', 'Date'].sort();
// ["Apple", "Banana", "Date", "Grape", "Guava", "Strawberry"]

For string values, it is fine. But you will get incorrect result for number sort -

let arr = [ 1, 2, 15 ];

// the method reorders the content of arr (and returns it)
arr.sort();

alert( arr );  // 1, 15, 2

As you can see, the result is incorrect. Because the items are sorted as strings by default. Literally, all elements are converted to strings and then compared. So, the lexicographic ordering is applied and indeed "2" > "15".

To use our own sorting order, we need to supply a function of two arguments as the argument of arr.sort().

The function should work like this:

function compare(a, b) {
  if (a > b) return 1;
  if (a == b) return 0;
  if (a < b) return -1;
}

For example -

function compareNumeric(a, b) {
  if (a > b) return 1;
  if (a == b) return 0;
  if (a < b) return -1;
}

let arr = [ 1, 2, 15 ];

arr.sort(compareNumeric);

alert(arr);  // 1, 2, 15

Now it works as intended.

Let’s step aside and see what’s happening. The arr can be array of anything, right? It may contain numbers or strings or html elements or whatever. We have a set of something. To sort it, we need an ordering function that knows how to compare its elements. The default is a string order. The arr.sort(fn) method has a built-in implementation of sorting algorithm. We don’t need to care how it exactly works (an optimized quicksort most of the time). It will walk the array, compare its elements using the provided function and reorder them, all we need is to provide the fn which does the comparison. The algorithm may compare an element multiple times in the process, but it tries to make as few comparisons as possible.

Actually, a comparison function is only required to return a positive number to say “greater” and a negative number to say “less”. And that allows us to write shorter version of the sort method -

let arr = [ 1, 2, 15 ];

arr.sort(function(a, b) { return a - b; });

alert(arr);  // 1, 2, 15

You can also make the code even more smaller using arrow function -

arr.sort( (a, b) => a - b );

[2,5,1,-4,6,9,-19,0].sort((a,b)=>a-b); // [-19, -4, 0, 1, 2, 5, 6, 9]

reverse()

The method arr.reverse() reverses the order of elements in arr and then returns it.

let arr = [1, 2, 3, 4, 5];
arr.reverse();

alert( arr ); // 5,4,3,2,1

split

The method str.split(delim) splits the string into an array by the given delimiter delim.

let names = 'Bilbo, Gandalf, Nazgul';

let arr = names.split(', ');

for (let name of arr) {
  alert( `A message to ${name}.` ); // A message to Bilbo  (and other names)
}

The split method has an optional second numeric argument – a limit on the array length. If it is provided, then the extra elements are ignored. In practice it is rarely used though:

let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2);

To split a string into array of character, use empty string -

"Santanu".split(""); // ["S", "a", "n", "t", "a", "n", "u"]

join()

The call arr.join(separator) does the reverse to split. It creates a string of arr items glued by separator between them.

let arr = ['Bilbo', 'Gandalf', 'Nazgul'];

let str = arr.join(';');

alert( str ); // Bilbo;Gandalf;Nazgul

'Santanu'.split("").join(""); // "Santanu"

Array.isArray

Arrays do not form a separate language type. They are based on objects. So typeof does not help to distinguish a plain object from an array:

alert(typeof {}); // object
alert(typeof []); // same

You can use Array.isArray() to check if something is an array.

alert(Array.isArray({})); // false

alert(Array.isArray([])); // true

Most methods support “thisArg”

Almost all array methods that call functions – like find, filter, map, with a notable exception of sort, accept an optional additional parameter thisArg.

arr.find(func, thisArg);
arr.filter(func, thisArg);
arr.map(func, thisArg);
// ...
// thisArg is the optional last argument

The value of thisArg parameter becomes this for func. For instance, here we use an object method as a filter and thisArg comes in handy:

let user = {
  age: 18,
  younger(otherUser) {
    return otherUser.age < this.age;
  }
};

let users = [
  {age: 12},
  {age: 16},
  {age: 32}
];

// find all users younger than user
let youngerUsers = users.filter(user.younger, user);

alert(youngerUsers.length); // 2

In the call above, we use user.younger as a filter and also provide user as the context for it. If we didn’t provide the context, users.filter(user.younger) would call user.younger as a standalone function, with this=undefined. That would mean an instant error.