Массив

Мы используем массив для хранения списка элементов. Обычно мы помещаем в массив элементы, которые являются реляционными, чтобы можно было легко перебирать элементы и что-то с ними делать.

Основы

Давайте начнем с создания массива. [] создает массив.

const a = [] // This is an empty array
Вход в полноэкранный режим Выход из полноэкранного режима

В массиве можно хранить любые данные. Например, мы можем хранить число 1, строку '2', функцию и пустой массив. Каждый элемент разделяется запятой ,.

const a = [1, '2', function hi(){}, []]
Вход в полноэкранный режим Выход из полноэкранного режима

Элементы массива упорядочены. Их индексы начинаются с 0. Мы можем использовать индекс для доступа к элементам следующим образом: a[0] вызывает элемент массива a с индексом 0.

const a = [1, '2', function hi(){}, []]
a[0] // 1
a[1] // '2'
a[2] // ƒ hi(){}
a[3] // []
Вход в полноэкранный режим Выход из полноэкранного режима

[] из a[0] не создает новый массив. Это синтаксис для доступа к элементу массива с заданным индексом.

Мы можем использовать .length для проверки количества элементов в массиве.

a.length // 4
Вход в полноэкранный режим Выйти из полноэкранного режима

Обратите внимание, что это свойство, а не метод (функция). Поэтому нам не нужен (), чтобы использовать его подобно функции a.length().

Если индекс массива начинается с 1, то индекс последнего элемента будет равен длине массива. Но мы уже знаем, что индекс массива начинается с 0. Поэтому, кроме пустого массива, индекс последнего элемента всегда будет на единицу меньше длины массива.

const a = [10, 20, 30]
a.length // 3
const lastItemIndex = a.length - 1
lastItemIndex // 2
const lastItem = a[lastItemIndex]
lastItem // 30
Вход в полноэкранный режим Выход из полноэкранного режима

Обновление массива

Мы все еще можем изменять (мутировать) массив после его создания.

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']
Войти в полноэкранный режим Выйти из полноэкранного режима

Мы также можем изменить или установить элемент по заданному индексу.

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']
Войти в полноэкранный режим Выход из полноэкранного режима

Если мы используем индекс для установки элемента и индекс больше, чем array.length, то в этих пропущенных индексах будут заполнены пустые слоты.

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

a.length // 11
Вход в полноэкранный режим Выход из полноэкранного режима

Чтобы избежать этой проблемы, мы можем не использовать для установки элемента индекс, который в данный момент не существует в массиве.

Каждый массив уникален

Следует помнить, что когда мы создаем массив, мы создаем новое значение. Здесь значение относится к области памяти в компьютере, а не к тому, что мы помещаем в массив.

const a = []
const b = []
console.log(a === b) // false
Вход в полноэкранный режим Выход из полноэкранного режима

Хотя a и b выглядят одинаково, это два значения в памяти. Поэтому они не равны.

С другой стороны, если у нас много переменных, ссылающихся на один и тот же массив, результат изменения этого массива отразится на этих переменных. Это потому, что все они указывают на одно и то же значение.

// 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']
Вход в полноэкранный режим Выход из полноэкранного режима

Обновление элемента

Мы можем использовать индекс для обновления элемента в массиве. Прежде чем это сделать, нам нужно найти индекс элемента. Для этого можно использовать array.indexOf().

array.indexOf() возвращает индекс первого найденного элемента. Затем мы можем использовать этот индекс для обновления элемента.

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() возвращает -1, если элемент не найден. Мы должны проверить, что возвращаемое число >= 0, прежде чем использовать его. В противном случае мы можем ввести тонкую ошибку в будущем. Потому что если мы случайно установим элемент в индекс -1, позже, когда мы используем array.indexOf(), чтобы найти элемент, но его не существует, и вернем -1, доступ к array[-1] найдет элемент, который был установлен случайно.

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']
Вход в полноэкранный режим Выход из полноэкранного режима

Представьте, что мы думаем, что нашли элемент, и начинаем работать над ним. Позже мы можем столкнуться с ошибкой, потому что элемент с отрицательным индексом не учитывается в массиве, что означает, что мы работали не над той целью.

Допустим, если мы хотим обновить все 'c' до 'ccc', нам придется повторить действие.

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']
Вход в полноэкранный режим Выход из полноэкранного режима

Что если мы не знаем заранее, сколько 'c' в массиве? Подобное действие не гарантирует, что оно сработает.

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'
Вход в полноэкранный режим Выйти из полноэкранного режима

Лучшим решением будет использование цикла.


Цикл по массиву

Циклы по массивам настолько распространены, что существует множество встроенных методов работы с массивами, которые помогают облегчить нам жизнь. Мы научимся использовать их шаг за шагом.

цикл for

Мы используем цикл for, чтобы сделать что-то несколько раз. Мы также можем использовать цикл for для перебора массива. Мы задаем let i = 0; в качестве начальной точки цикла for, потому что индекс начинается с 0.
Мы задаем условие i <= a.length - 1;, потому что индекс последнего элемента равен a.length - 1. Задаем i++ увеличивая i на единицу при каждом выполнении, так как индекс массива также увеличивается на 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])
}
Вход в полноэкранный режим Выход из полноэкранного режима

Таким образом, мы меняем цикл для каждого элемента массива.

Давайте вернемся к нашему предыдущему примеру и с помощью цикла for обновим все c до 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']
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь, независимо от количества вхождений ‘c’ в массиве, мы можем использовать этот цикл for для обновления их до ‘ccc’.

Обычно мы не создаем массив и не выполняем цикл по нему сразу. Давайте обернем этот цикл for в функцию, чтобы использовать его позже.

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']
Вход в полноэкранный режим Выход из полноэкранного режима

В настоящее время функция работает только с массивом a и обновляет c до ccc. Мы можем сделать функцию более многоразовой. Начнем с того, что она будет принимать другие массивы. Добавьте параметр array к updateItemInArray и измените a на array в цикле for.

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']
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь функция может обновить ‘c’ до ‘ccc’ в любом массиве. Это звучит не очень полезно. Вместо того чтобы решать, как изменить элементы непосредственно внутри функции updateItemInArray, будет лучше, если мы сможем решить это при вызове функции.

Мы можем добиться этого, используя обратный вызов (функцию). Добавьте второй параметр callback к updateItemInArray. При каждом запуске цикла мы передаем текущий элемент array[i] в обратный вызов и заменяем элемент с этим индексом на то, что вернется из обратного вызова.

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]) 
  }
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Мы можем определить функцию и передать ее в качестве обратного вызова 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']
Войти в полноэкранный режим Выйти из полноэкранного режима

Или мы можем сделать встроенную функцию при вызове updateItemInArray.

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

  return item
})

a // ['a', 'b', 'e', 'd', 'e']
Войти в полноэкранный режим Выход из полноэкранного режима

Отлично! updateItemInArray становится легко использовать повторно. Есть еще одна вещь, которую мы можем сделать. Мы можем передать больше информации обратному вызову, чтобы обратный вызов мог получить доступ к этой информации, когда это необходимо.

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']
Вход в полноэкранный режим Выход из полноэкранного режима

Методы массивов

Существует множество встроенных методов массивов, которые служат для различных целей. Они устроены аналогично тому, который мы только что создали. Мы передаем функции обратный вызов. Как этот обратный вызов будет использоваться, определяется внутри функции.

если обратный вызов возвращает true, array.find(callback) вернет этот элемент

const a = ['a', 'b', 'c', 'd', 'c']
const result = a.find((item) => item === 'b')
result // 'b'
Вход в полноэкранный режим Выйти из полноэкранного режима

если callback возвращает true, array.filter(callback) вернет новый массив с этими элементами.

const a = ['a', 'b', 'c', 'd', 'c']
const result = a.filter((item, index) => index < 3)
result // ['a', 'b', 'c']
Войти в полноэкранный режим Выход из полноэкранного режима

Аналогично нашему updateItemInArray, array.map(callback) вернет массив с элементами, которые вернет обратный вызов, но array.map(callback) не изменит исходный массив.

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"}
]
Вход в полноэкранный режим Выход из полноэкранного режима

Как и array.map(callback), некоторые методы работы с массивами не изменяют исходный массив, но мы все равно можем случайно изменить его. Обычно это происходит, когда элементы являются объектами. Например:

// 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"}
]
Войти в полноэкранный режим Выйти из полноэкранного режима

Трудно объяснить, почему изменение оригинала может вызвать проблемы, если вы с ними не сталкивались. Идея заключается в том, что мы непреднамеренно изменяем что-то, что приведет к неожиданным результатам позже в нашей программе.

Существуют более полезные встроенные методы массивов, но я оставлю это на ваше усмотрение. После того как мы поймем, почему некоторые методы массива принимают обратный вызов, научиться использовать их станет намного проще.


Подведение итогов

В этой статье мы изучили некоторые базовые операции с массивами. Мы узнали о некоторых видах поведения массива. И самое главное — мы узнали, как выполнять цикл по массиву. Примеры не являются практическими, но я думаю, что их достаточно, чтобы продемонстрировать, как все работает. После того как мы узнаем, что может делать массив, мы будем знать, когда и как его использовать.

Оцените статью
devanswers.ru
Добавить комментарий