В этом блоге мы рассмотрим некоторые методы, которые мы можем использовать для перебора массива в JavaScript: простой цикл for, цикл while и методы: forEach, map и filter.

Я почти два месяца в моем путешествии по Launch School, и я закончил изучение всего материала в JS110. Так что теперь речь идет только о подготовке к оценке и о том, чтобы все, что я узнал, действительно усвоилось, а также о попытке заполнить все пробелы в моих знаниях. Я решил написать этот блог, чтобы проверить свое понимание этих важных концепций из JS110 и в то же время узнать о них больше.

Перебор массива с помощью цикла for

Цикл for — универсальный инструмент, который мы можем использовать в нашем коде. Это итеративный метод, который перебирает блок кода до тех пор, пока определенное условие оценивается как true. Он обычно используется для перебора массивов, поскольку один из трех компонентов является начальной точкой (или переменной counter), которая позволяет отслеживать индексы в массиве. Два других компонента — это условие, выполнение которого приводит к выполнению кода внутри цикла (а если нет, то цикл завершается), и инкрементатор. >, который увеличивает начальную точку на указанное значение для каждой итерации.

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

for (let idx = 0; idx < arr.length; idx++) {
  if (arr[idx] % 2 === 0) {
    console.log(arr[idx] * 2);
  }
}

// Outputs:
// 4
// 8
// 12

В этом фрагменте кода мы используем цикл for для повторения столько раз, сколько длина массива arr. Таким образом, наш цикл for повторится шесть раз. На каждой итерации мы входим в тело цикла, которое содержит оператор if с одним условием и одним предложением. Условие извлекает элемент из arr в idx позиции и проверяет, является ли его значение четным или нечетным. Если выражение оценивается как true, предложение выполняется, и мы видим, что arr[idx] * 2 выводится на консоль. Таким образом, конечный результат будет 4, 8 и затем 12 соответственно.

Давайте рассмотрим другой пример с многомерными массивами и вложенными for циклами.

let arr = [['hi'], ['bye', 'bar']];

for (let idx = 0; idx < arr.length; idx++) {
  for (let jdx = 0; jdx < arr[idx].length; jdx++) {
    arr[idx][jdx] = 'foo';
  }
}

console.log(arr); // [['foo'], ['foo', 'foo']]

В этом примере мы изменяем подмассивы внутри массива arr, используя вложенные циклы for. Внешний цикл for перебирает внешний массив, а внутренний цикл for перебирает подмассивы, чтобы мы могли получить доступ к элементам. В теле внутреннего цикла мы ссылаемся на элемент в позиции jdxth и переназначаем его значение строке ‘foo’.

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

Перебор массива с помощью цикла while

Цикл while работает аналогично циклу for в том смысле, что он повторяется до тех пор, пока предустановленное условие оценивается как true, однако он отличается тем, что его единственным компонентом является условие. Синтаксически это очень похоже на оператор if.

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

let idx = 0;
while (idx < arr.length) {
  if (arr[idx] % 2 === 0) {
    console.log(arr[idx] * 2);
  }
  idx += 1;
}

// Outputs:
// 4
// 8
// 12

Этот пример кода производит тот же результат, что и предыдущий пример, который мы рассматривали с использованием цикла for. В этом фрагменте мы объявляем переменную с именем idx вне цикла while, что позволяет нам отслеживать, на каком индексе и итерации мы находимся. В теле цикла while мы используем оператор if, чтобы проверить, является ли элемент четным или нечетным. Если оно четное, выполняется оператор console.log(arr[idx] * 2). После оператора if мы увеличиваем переменную idx на 1 для каждой итерации, чтобы не попасть в вечный цикл (цикл повторяется бесконечно из-за того, что условие никогда не выполняется). Конечным результатом будет 4, 8, а затем 12.

let arr = [1, 2, 3];

let idx = 0;
while (idx < arr.length) {
  console.log(arr[idx]);
}

// Output
// 1
// 1
// 1
// 1
// 1
// ... And this goes on forever and ever. Ctrl + C out of there!!!

Вот краткий пример вечного цикла, о котором я говорил ранее. idx никогда не переназначается в любой точке кода, поэтому idx всегда будет меньше длины массива (3), и условие никогда не выполняется, поэтому цикл никогда не завершается. Вот почему мы продолжаем видеть, как элемент с индексом 0th в arr постоянно выводится на консоль.

Петли while красивы и просты, и, как и классическая петля for, очень универсальны по своей функциональности.

Перебор массива с использованием метода forEach

В JavaScript есть встроенный метод, который позволяет нам перебирать массив так же, как в цикле for, но вместо этого он позволяет нам делать это более кратко и, зачастую, всего в одной строке кода. . Это встроенный метод forEach, который представляет собой итеративный метод, который принимает функцию обратного вызова в качестве аргумента и вызывает обратный вызов столько раз, сколько элементов в массиве (если только массив не изменяется в обратном вызове, что мы увидим ниже). в примере немного). Обратный вызов определяется тремя параметрами: текущим элементом массива, индексом этого элемента и массивом, с которым мы имеем дело. Последние два параметра являются необязательными. forEach, а также другие встроенные итерационные методы, которые мы рассмотрим, принимают необязательный второй аргумент: значение this. Поскольку мы рассматриваем только перебор массивов, мы пока не будем беспокоиться о значении this.

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

Хорошо, давайте посмотрим на пример в коде.

let array = [1, 2, 3, 4, 5, 6];

array.forEach(function(element) {
  if (element % 2 === 0) {
    console.log(element * 2);
  }
});

// Outputs:
// 4
// 8
// 12

Этот пример показывает, как мы можем использовать forEach так же, как циклы for и while. Этот фрагмент кода дает тот же результат и вывод, что и первые рассмотренные нами примеры с циклами for и while. Мы определили функцию обратного вызова в качестве аргумента для forEach, которая вызывается для каждого элемента в массиве, и еще раз, если это четное число, мы выводим значение элемента, умноженное на два.

Теперь давайте посмотрим, что произойдет, если мы попытаемся присвоить возвращаемое значение forEach переменной.

let array = [1, 2, 3, 4, 5, 6];

let newArray = array.forEach((el) => (el % 2 === 0 ? console.log(el * 2) : el));

console.log(newArray);

// Outputs:
// 4
// 8
// 12
// ...?

Как вы думаете, каким будет значение newArray?

Подумайте об этом на секунду…

Если вы ответили undefined, поздравляем! Вы понимаете возвращаемое значениеforEach. Возвращаемое значение обратного вызова не используется forEach. Работа forEach состоит только в том, чтобы выполнить обратный вызов столько раз, сколько есть элементов, и это все.

А теперь немного forEach странностей. Что произойдет, если мы изменим массив внутри тела функции обратного вызова?

let array = [1, 2, 3, 4, 5, 6];

array.forEach((element, idx) => {
  if (element % 2 === 0) {
    console.log(element * 2);
  }
  array.shift();

  console.log('Iteration' + (idx + 1));
});

// Outputs:
// Iteration 1
// Iteration 2
// Iteration 3

Хотя массив, для которого мы вызвали метод forEach, состоит из 6 элементов, forEach завершается итерацией только 3 раза, как мы можем видеть в выводе на консоль. Почему это? И почему код внутри оператора if никогда не выполняется? Это потому, что мы используем метод shift в нашем массиве внутри обратного вызова, который является деструктивным методом, который удаляет первый элемент из массива, для которого он вызывается.

Вот что происходит во время каждой итерации:

Итерация 1: element ссылается на элемент 1 с индексом 0 из array. Метод shift удаляет первый элемент array (1) и “Iteration 1” выводится в консоль из строки: console.log(‘Iteration’ + (idx + 1)); (где idx + 1 эквивалентно 1). array теперь [2, 3, 4, 5, 6].

Итерация 2: element ссылается на элемент 3 с индексом 1 из array. Таким образом, он фактически «пропускает» элемент 2, поскольку 2 теперь находится по индексу 0. Метод shift удаляет первый элемент array (2), и “Iteration 2” выводится на консоль. array теперь [3, 4, 5, 6].

Итерация 3:element ссылается на элемент 5 с индексом 2 из array. Метод shift удаляет первый элемент array, который теперь равен 3, и “Iteration 3” выводится на консоль. array теперь [4, 5, 6].

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

Одна вещь, которая действительно помогла мне понять, как ведут себя forEach и другие итерационные методы, заключалась в том, что я думал о них как о for циклах, потому что, в конечном счете, это то, чем они являются под поверхностью (эти функции были созданы с использованием for циклов). forEach проверяет индекс элемента на соответствие длине массива и вызывает обратный вызов только в том случае, если индекс меньше длины. Поэтому, когда мы используем деструктивный метод внутри обратного вызова, который изменяет длину массива, например метод shift, мы фактически изменяем условие цикла и количество вызовов обратного вызова.

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

Итерация по массиву с использованием метода `map`

В JavaScript есть еще один встроенный метод, который позволяет нам перебирать элементы массива: map. Как и forEach, map принимает в качестве аргумента функцию обратного вызова, которая вызывается для каждого элемента массива. Обратный вызов определяется тремя параметрами: текущим элементом, индексом этого элемента (необязательно) и массивом (необязательно). Однако map возвращает новый массив преобразованных элементов на основе возвращаемого значения функции обратного вызова.

let array = [1, 2, 3, 4, 5, 6];

let newArray = array.map(function(element) {
  if (element % 2 === 0) {
    return element * 2;
  } else {
    return element;
  }
});

console.log(newArray);
// [1, 4, 3, 8, 5, 12]

В этом примере кода map вызывает обратный вызов столько раз, сколько имеется элементов (шесть раз), и все, что возвращается из обратного вызова, становится «преобразованным» элементом в нашем новом массиве. Внутри обратного вызова мы определили оператор if/else, который проверяет, является ли элемент четным, и если это так, обратный вызов возвращает значение этого элемента, умноженное на два. В противном случае он возвращает элемент как есть. Вот почему наш новый массив, который наконец возвращает map: [1, 4, 3, 8, 5, 12].

Теперь давайте посмотрим на новый пример, где мы изменяем массив внутри обратного вызова.

let array = ['a', 'b', 'c'];

let newArray = array.map((el, idx) => array.unshift(idx));

console.log(array); // What do you think gets outputted?
console.log(newArray); // What do you think gets outputted?

Здесь мы вызвали метод unshift внутри обратного вызова для array. unshift — это деструктивный метод, который добавляет указанный элемент в начало массива, для которого он вызывается, и возвращаетновую длину массива. Как мы думаем, какое значение имеет array к концу строки 3?

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

Поскольку возвращаемое значение unshift представляет собой новую длину измененного массива, эти значения станут преобразованными элементами нового массива, который возвращает map. Давайте посмотрим, что происходит во время каждой итерации:

Итерация 1: idx (эквивалентно 0, поскольку мы находимся в первом элементе) добавляется к началу array, и array теперь выглядит как [0, ‘a’, ‘b’, ‘c’]. Новая длина array равна 4, поскольку unshift возвращает новую длину массива, а обратный вызов возвращает возвращаемое значение unshift (немного многословно), возвращаемое значение обратного вызова в конечном итоге… как вы уже догадались… 4. newArray теперь [4].

Итерация 2: idx добавляется к началу array, поэтому array теперь равно [1, 0, ‘a’, ‘b’, ‘c’]. Новая длина array равна 5, поэтому обратный вызов вернет возвращаемое значение unshift (которое равно 5). newArray теперь [4, 5].

Итерация 3:idx добавляется к началу array, поэтому array теперь равно [2, 1, 0, ‘a’, ‘b’, ‘c’]. Новая длина array равна 6, поэтому обратный вызов вернет возвращаемое значение unshift (которое равно 6). newArray теперь [4, 5, 6].

И затем, после трех циклов, map прекращает итерацию. Несмотря на то, что длина array увеличилась, map игнорировал любые изменения, внесенные в исходный массив во время итерации. map всегда будет повторяться столько раз, сколько элементов в исходном массиве, и возвращать новый массив длины исходного массива.

map — полезный метод, особенно когда мы хотим преобразовать массив, не изменяя его.

Итерация по массиву с использованием метода `filter`

Теперь перейдем к третьему итеративному методу, встроенному в JavaScript, который мы будем изучать сегодня: filter. Так же, как forEach и map, он принимает функцию обратного вызова в качестве аргумента. Обратный вызов определяется тремя параметрами: текущий элемент в массиве, индекс текущего элемента (необязательный) и массив, к которому был вызван filter (также необязательный). Как и map, filter возвращает новый массив, однако этот массив основан на правдивости возвращаемого значения обратного вызова. Таким образом, обратный вызов действует как функция тестирования, чтобы определить, какие элементы выбраны для добавления в новый массив, который возвращает filter. Если возвращаемое значение обратного вызова истинно, оно добавляется в новый массив. Если возвращаемое значение обратного вызова ложно, оно не добавляется в новый массив.

Давайте посмотрим, как filter работает в коде:

let array = [1, 2, 3, 4, 5, 6];

let newArray = array.filter(el => el % 2 === 0);

console.log(newArray); // [2, 4, 6]

Как мы видим, массив, возвращаемый filter, равен [2, 4, 6], поскольку предоставленная функция тестирования (обратный вызов) проверяет, является ли выражение el % 2 === 0 истинным. И если это так, он добавляется в наш новый массив.

Давайте посмотрим, что происходит во время первых трех итераций:

Итерация 1: el содержит значение первого элемента массива, 1. Мы проверяем, если 1 % 2 === 0, а это не так, поэтому выражение оценивается как false, и элемент не выбирается. Пока что наш новый массив выглядит так: [].

Итерация 2: el содержит значение второго элемента массива, 2. Мы проверяем, есть ли 2 % 2 === 0, и это так, поэтому выражение оценивается как true, элемент выбирается и добавляется в новый массив, который теперь выглядит так: [2].

Итерация 3:el содержит значение третьего элемента массива, 3. Мы проверяем, если 3 % 2 === 0, и это не так, поэтому выражение оценивается как false, и элемент не выбирается, что означает, что наш новый массив все еще выглядит так же, как и в последней итерации: [2].

Этот процесс продолжается таким же образом, пока мы не дойдем до последнего элемента array и, таким образом, до последней итерации filter.

В отличие от map, количество итераций метода filter может меняться в зависимости от того, изменяет ли массив функция обратного вызова.

let arr = ['a', 'b', 'c'];
let newArr = arr.filter((el, idx) => {
  console.log(idx);
  arr.pop());
});

console.log(newArr); // ['a', 'b']
console.log(arr); // ['a']

Ну, это какой-то странный вывод… Давайте разберем этот пример итерацию за итерацией:

Итерация 1: el содержит значение ‘a’, и обратный вызов вызывается с ‘a’, переданным в качестве аргумента. idx содержит индекс текущего элемента, который равен 0, так как мы находимся на первой итерации. Затем pop удаляет ‘c’ из конца массива и возвращает только что удаленный элемент. Поскольку ‘c’ является истинным значением, текущий элемент из arr добавляется в наш новый массив. Таким образом, arr ссылается на массив [‘a’, ‘b’], длина которого теперь равна 2, а newArr ссылается на массив [‘a’].

Итерация 2: el содержит значение ‘b’, и обратный вызов вызывается с ‘b’, переданным в качестве аргумента. idx содержит индекс текущего элемента, который равен 1. Поэтому, когда вызывается обратный вызов и выполняется строка console.log(idx), в консоль выводится 1. Затем pop удаляет ‘b’ из конца массива и возвращает только что удаленный элемент. Поскольку ‘b’ является истинным значением, текущий элемент из arr добавляется в наш новый массив. Итак, arr теперь ссылается на массив [‘a’], а newArr ссылается на массив [‘a’, 'b'].

К концу второй итерации для filter больше нет элементов для итерации, так как нет элемента, соответствующего индексу 2. Подумайте о том, как цикл for и while проверяет элемент на соответствие условию, и только если оно выполняется, тело цикла выполняется. Как я упоминал ранее, условие таково: пока index меньше длины массива, продолжайте итерацию. На третьей итерации index равно 2, а длина равна 1. Поскольку index больше длины массива, условие больше не выполняется, поэтому обратный вызов не вызывается в третий раз. Наконец, filter возвращает новый массив: [‘a’, ‘b’]. И arr остается таким: [‘a’].

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

Надеюсь, у вас появилось немного больше ясности (и, возможно, некоторые дополнительные вопросы) о поведении filter, потому что, очевидно, оно может быть довольно странным.

Заключение

Итерация по массивам — фундаментальная часть программирования, и по мере того, как я стал более комфортно использовать эти различные методы, я смог применять их при решении проблем кодирования и создании программ. Я надеюсь, что это дало вам хорошее представление о том, как работают эти методы и как вы можете начать использовать их в своем собственном коде. Удачи!