DEV Community

Gohomewho
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
Enter fullscreen mode Exit fullscreen mode

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(){}, []]
Enter fullscreen mode Exit fullscreen mode

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] // []
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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])
}
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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]) 
  }
}
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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"}
]
Enter fullscreen mode Exit fullscreen mode

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"}
]
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)