Gohomewho

Posted on

# Array

We use array to store a list of items. Generally, we put items that are relational in an array, so we can easily loop through the items and do something with them.

## Basics

Let's start by creating an array. `[]` creates an array.

``````const a = [] // This is an empty array
``````

We can store any kind of data inside an array. For example, we can store a number `1`, a string `'2'`, a function, and an empty array. Each item is separated by a comma `,`.

``````const a = [1, '2', function hi(){}, []]
``````

Array items are ordered. Their indexes start from 0. We can use index to access item like this, `a[0]` accesses `index 0` item of array `a`.

``````const a = [1, '2', function hi(){}, []]
a[0] // 1
a[1] // '2'
a[2] // ƒ hi(){}
a[3] // []
``````

The `[]` of `a[0]` doesn't create a new array. It's the syntax to access an item in an array with a given index.

We can use `.length` to check how many items that an array has.

``````a.length // 4
``````

Note that this is a property not a method (function). So we don't need `()` to use it like a function `a.length()`.

If array index starts from 1, then the index of the last item will equal to the length of an array. But we already know that array index starts from 0. So except empty array, the index of the last item will always be one less than the array length.

``````const a = [10, 20, 30]
a.length // 3
const lastItemIndex = a.length - 1
lastItemIndex // 2
const lastItem = a[lastItemIndex]
lastItem // 30
``````

### Update array

We can still change (mutate) an array after we create it.

``````const a = [1, '2']

// array.push() can add an item or items at the end of the array
a.push('hi')

// array.unshift() can add an item or items at the start of the array.
a.unshift('hello')

a // ['hello', 1, '2', 'hi']

// array.pop() removes the last item from the array and returns that item
const removedLastItem = a.pop()
removedLastItem  // 'hi'

// array.shift() removes the first item from the array and returns that item
const removedFirstItem = a.shift()
removedFirstItem // 'hello'

a // [1, '2']
``````

We can also change or set an item at a given index.

``````const a = [1, '2']
a[0] = 5
a[1] = '6'
a // [5, '6']

// add a item at index 2 which is the last item in this case
a[2] = 7
a // [5, '6', 7]

// this is like using a.push('8')
a[a.length] = '8'
a // [5, '6', 7, '8']
``````

If we use index to set an item and the index is higher than the `array.length`, then there will be empty slots filled in those skipped indexes.

``````a[10] = 10
a // output from chrome devtool console [5, '6', 7, '8', empty × 6, 10]

a.length // 11
``````

We can avoid using an index that doesn't currently exist in the array for setting item to avoid this issue.

### Each array is unique

One thing to keep in mind is that when we create an array, we create a new value. Here, value refers to a memory space in the computer, not what we put inside an array.

``````const a = []
const b = []
console.log(a === b) // false
``````

Although `a` and `b` look identical, they are two values in the memory. That's why they are not equal.

On the other hand, if we have many variables reference to the same array, the result of changing that array will be reflected to those variables. That's because they all points to the same value.

``````// all variables point to the same array
const a = []
const b = a
const c = b

// change that array through variable c
c.push('hi','how','are','you')

// the result is reflected to all variables
c // ['hi', 'how', 'are', 'you']
b // ['hi', 'how', 'are', 'you']
a // ['hi', 'how', 'are', 'you']
``````

## Update item

We can use index to update an item in the array. Before doing that, we need to find the index of an item. We can use `array.indexOf()` to find it.

`array.indexOf()` returns the index of first matched item. We can then use that index to update the item.

``````const a = ['a', 'b', 'c', 'd', 'c']
const targetIndex = a.indexOf('c')
targetIndex // 2
a[targetIndex] = 'ccc'
a // ['a', 'b', 'ccc', 'd', 'c']
``````

`array.indexOf()` returns `-1` if it doesn't find the item. We should check that returned number >= 0 before using it. Otherwise, we might introduce subtle bug in the future. Because if we accidentally set an item at index `-1`, later when we use `array.indexOf()` to find an item but it doesn't exist and return -1, access `array[-1]` will find the item that was set accidentally.

``````const a = ['a', 'b', 'c', 'd']

// 'e' doesn't exist in array, so this will return -1
const targetIndex = a.indexOf('e')

// we meant to update 'e' to 'g'
// but accidentally set an item at index -1
a[targetIndex] = 'g'

// a.indexOf('g') return -1, but it doesn't actually find the index of 'g'
// it returns -1 because a.indexOf('g') cannot find the item
const newTargetIndex = a.indexOf('g')
newTargetIndex // -1
a[newTargetIndex] // 'g'

// making change to the item
a[newTargetIndex] = 'hi'

// array.includes() can check if an item exist in the array
// we just set an item 'hi'. why doesn't it exist in the array?
a.includes('hi') // false

// because negative indexed item doesn't get counted.
a.length // 4

a // output from chrome devtool console ['a', 'b', 'c', 'd', -1: 'hi']
``````

Imagine that we think we find the item and start working on it. Later, we could run into some bugs because the negative indexed item doesn't count in the array which means we've been working on a wrong target.

Suppose we want to update all `'c'` to `'ccc'`, we'll need to repeat the action.

``````const a = ['a', 'b', 'c', 'd', 'c']

let targetIndex = a.indexOf('c')
targetIndex // 2
a[targetIndex] = 'ccc'
a // ['a', 'b', 'ccc', 'd', 'c']

targetIndex = a.indexOf('c')
targetIndex // 4
a[targetIndex] = 'ccc'
a // ['a', 'b', 'ccc', 'd', 'ccc']
``````

What if we don't know the how many `'c'` in the array in advance? Doing something like this doesn't guarantee to work.

``````a[a.indexOf('c')] = 'ccc'
a[a.indexOf('c')] = 'ccc'
a[a.indexOf('c')] = 'ccc'
a[a.indexOf('c')] = 'ccc'
a[a.indexOf('c')] = 'ccc'
``````

A better solution to is using a loop.

## Loop through array

Looping through arrays is so common that there are many built-in array methods that help make our lives easier. We'll learn how to use them step by step.

### for loop

We use for loop to do something multiple times. we can also use for loop to loop through an array. We set `let i = 0;` as the starting point of for loop, because index start from 0.
We set the condition to `i <= a.length - 1;` because the index of last item is `a.length - 1`. We set `i++` increase i by one each run because array index also increase by 1.

``````const a = [1, 2, 3, 4, 5]

for (let i = 0; i <= a.length - 1; i++) {
console.log(a[i])
}

// only change the condition to i < a.length
// it's same as above
for (let i = 0; i < a.length; i++) {
console.log(a[i])
}
``````

This way, we change loop through every item in an array.

Let's bring back our previous example and use for loop to update all `c` to `ccc`.

``````const a = ['a', 'b', 'c', 'd', 'c']

for (let i = 0; i < a.length; i++) {
// check if the item at index i is 'c'
const isTarget = a[i] === 'c'

// if true, update the item at index i
if(isTarget) {
a[i] = 'ccc'
}
}

a // ['a', 'b', 'ccc', 'd', 'ccc']
``````

Now, no matter how many occurrences of 'c' in the array, we can use this for loop to update them to 'ccc'.

Usually, we won't create an array and loop through it immediately. Let's wrap that for loop inside a function so we can use it later.

``````const a = ['a', 'b', 'c', 'd', 'c']

function updateItemInArray(){
for (let i = 0; i < a.length; i++) {
const isTarget = a[i] === 'c'

if(isTarget) {
a[i] = 'ccc'
}
}
}

// imaging we run so much other code here...

// at some point we want to run our for loop
updateItemInArray()

a // ['a', 'b', 'ccc', 'd', 'ccc']
``````

Currently, the function only works on array `a` and update `c` to `ccc`. We can make the function more reusable. Let's start by making it take other arrays. Add a parameter called `array` to `updateItemInArray` and change `a` to `array` in for loop.

``````const a = ['a', 'b', 'c', 'd', 'c']

function updateItemInArray(array){
for (let i = 0; i < array.length; i++) {
const isTarget = array[i] === 'c'

if(isTarget) {
array[i] = 'ccc'
}
}
}

updateItemInArray(a) // pass variable a to the function

a // ['a', 'b', 'ccc', 'd', 'ccc']
``````

Now the function can update 'c' to 'ccc' on any array. This doesn't sound very useful. Instead of deciding how to change the items directly inside the function `updateItemInArray`, it will be better if we can decide it when we call the function.

We can achieve that by using a callback (function). Add a second parameter called `callback` to `updateItemInArray`. On Each run of the loop, we pass the current item `array[i]` to the callback and replace the item at that index with whatever return from the callback

``````const a = ['a', 'b', 'c', 'd', 'c']

function updateItemInArray(array, callback){
for (let i = 0; i < array.length; i++) {
// callback(array[i]) pass current item to callback
// array[i] = callback(array[i]) replace current item with the returned value from callback
array[i] = callback(array[i])
}
}
``````

We can define a function and pass it as a callback to `updateItemInArray`.

``````const a = ['a', 'b', 'c', 'd', 'c']

function updateItemInArray(array, callback){ // ... }

function myCallback(item){
if(item === 'c') {
return 'ccc'
}

return item
}

updateItemInArray(a, myCallback)

a // ['a', 'b', 'ccc', 'd', 'ccc']
``````

Or we can make an inline function when we call `updateItemInArray`.

``````updateItemInArray(a, (item) => {
if(item === 'ccc') {
return 'e'
}

return item
})

a // ['a', 'b', 'e', 'd', 'e']
``````

Nice! `updateItemInArray` becomes easy to be reused. There is one more thing we can do. We can pass more information to callback, so the callback can access those information when needed.

``````function updateItemInArray(array, callback){
for (let i = 0; i < array.length; i++) {
// add extra arguments to callback
array[i] = callback(array[i], i, array)
}
}

// if we only care about item and index in our callback
// we can skip the third parameter when defining the callback
updateItemInArray(a, (item, index) => index + item)

a // ['0a', '1b', '2e', '3d', '4e']
``````

### Array methods

There are many build-in array methods that serve different purpose. They are designed similar to the one we just made. We pass a callback to the function. How that callback is used is defined inside the function.

if callback return true, `array.find(callback)` will return that item

``````const a = ['a', 'b', 'c', 'd', 'c']
const result = a.find((item) => item === 'b')
result // 'b'
``````

if callback return true, `array.filter(callback)` will return a new array with those item.

``````const a = ['a', 'b', 'c', 'd', 'c']
const result = a.filter((item, index) => index < 3)
result // ['a', 'b', 'c']
``````

Similar to our `updateItemInArray`, `array.map(callback)` will return an with items of whatever the callback returns, but `array.map(callback)` doesn't change the original array.

``````const a = ['a', 'b', 'c', 'd', 'c']
const result = a.map((item) => {
return { name: item }
})
result // [
{"name": "a"},
{"name": "b"},
{"name": "c"},
{"name": "d"},
{"name": "c"}
]
``````

Like `array.map(callback)`, some array methods don't change the original array, but we might still accidentally change it. This usually happens when the items are objects. For example:

``````// we have an array of objects
const a = [
{"name": "a"},
{"name": "b"},
{"name": "c"},
{"name": "d"},
{"name": "c"}
]

// we want to make a copy of the array and change some properties of each item
const result = a.map((item, index) => {
// accidentally change a property of the item of this array
item.name = index + item.name

// return a new object with new value
return { name: item.name }
})

// we get what we want
result // [
{"name": "0a"},
{"name": "1b"},
{"name": "2c"},
{"name": "3d"},
{"name": "4c"}
]

// but we also change the original array
a // [
{"name": "0a"},
{"name": "1b"},
{"name": "2c"},
{"name": "3d"},
{"name": "4c"}
]
``````

It's hard to explain why changing original can cause problems if you haven't encountered any. The idea is that we unintentionally change something which will lead to unexpected results later in our program.

There are more useful build-in array methods, but I'll leave it for you to explore. After we understand why some array methods accept a callback, learning how to use them becomes so much easier.

## Wrap up

In this article, we learn some basic operations of array. We learn some behaviors of array. And the most importantly, we learn how to loop through an array. The examples are not practical, but I think they are enough to demonstrate how things work. After we learn what array can do, we will know when and how to use them.