В этом блоге мы рассмотрим некоторые методы, которые мы можем использовать для перебора массива в 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
перебирает подмассивы, чтобы мы могли получить доступ к элементам. В теле внутреннего цикла мы ссылаемся на элемент в позиции jdx
th и переназначаем его значение строке ‘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), и условие никогда не выполняется, поэтому цикл никогда не завершается. Вот почему мы продолжаем видеть, как элемент с индексом 0
th в 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
, потому что, очевидно, оно может быть довольно странным.
Заключение
Итерация по массивам — фундаментальная часть программирования, и по мере того, как я стал более комфортно использовать эти различные методы, я смог применять их при решении проблем кодирования и создании программ. Я надеюсь, что это дало вам хорошее представление о том, как работают эти методы и как вы можете начать использовать их в своем собственном коде. Удачи!