> Часть 1: первая программа, особенности языка, стандарты
> Часть 2: стиль кода и структура программ
> Часть 3: переменные, типы данных, выражения, объекты
> Часть 4: функции
> Часть 5: массивы и циклы
> Часть 6: исключения, точка с запятой, шаблонные литералы
Массивы
Массивы, объекты типа
Array
, развиваются вместе с остальными механизмами языка. Они представляют собой списки пронумерованных значений.Первый элемент массива имеет индекс (ключ) 0, такой подход используется во многих языках программирования.
В этом разделе мы рассмотрим современные методы работы с массивами.
?Инициализация массивов
Вот несколько способов инициализации массивов.
const a = []
const a = [1, 2, 3]
const a = Array.of(1, 2, 3)
const a = Array(6).fill(1) //инициализация каждого элемента массива, состоящего из 6 элементов, числом 1
Для того чтобы получить доступ к отдельному элементу массива, используют конструкцию, состоящую из квадратных скобок, в которых содержится индекс элемента массива. Элементы массивов можно как считывать, так и записывать.
const a = [1, 2, 3]
console.log(a) //[ 1, 2, 3 ]
const first = a[0]
console.log(first) //1
a[0] = 4
console.log(a) //[ 4, 2, 3 ]
Конструктор
Array
для объявления массивов использовать не рекомендуется.const a = new Array() //не рекомендуется
const a = new Array(1, 2, 3) //не рекомендуется
Этот способ следует использовать лишь при объявлении типизированных массивов.
?Получение длины массива
Для того чтобы узнать длину массива, нужно обратиться к его свойству
length
.const l = a.length
?Проверка массива с использованием метода every()
Метод массивов
every()
можно использовать для организации проверки всех их элементов с использованием некоего условия. Если все элементы массива соответствуют условию, функция возвратит true
, в противном случае она возвратит false
.Этому методу передаётся функция, принимающая аргументы
currentValue
(текущий элемент массива), index
(индекс текущего элемента массива) и array
(сам массив). Он может принимать и необязательное значение, используемое в качестве this
при выполнении переданной ему функции.Например, проверим, превышают ли значения всех элементов массива число 10.
const a = [11, 12, 13]
const b = [5, 6, 25]
const test = el => el > 10
console.log(a.every(test)) //true
console.log(b.every(test)) //false
Здесь нас, в функции
test()
, интересует лишь первый передаваемый ей аргумент, поэтому мы объявляем её, указывая лишь параметр el
, в который и попадёт соответствующее значение.?Проверка массива с использованием метода some()
Этот метод очень похож на метод
every()
, но он возвращает true
, если хотя бы один из элементов массива удовлетворяет условию, заданному переданной ему функцией.?Создание массива на основе существующего массива с использованием метода map()
Метод массивов
map()
позволяет перебирать массивы, применяя к каждому их элементу, переданную этому методу, функцию, преобразующую элемент, и создавать из полученных значений новые массивы. Вот, например, как получить новый массив, являющийся результатом умножения всех элементов исходного массива на 2.const a = [1, 2, 3]
const double = el => el * 2
const doubleA = a.map(double)
console.log(a) //[ 1, 2, 3 ]
console.log(doubleA) //[ 2, 4, 6 ]
?Фильтрация массива с помощью метода filter()
Метод
filter()
похож на метод map()
, но он позволяет создавать новые массивы, содержащие лишь те элементы исходных массивов, которые удовлетворяют условию, задаваемому передаваемой методу filter()
функцией.?Метод reduce()
Метод
reduce()
позволяет применить заданную функцию к аккумулятору и к каждому значению массива, сведя массив к единственному значению (это значение может иметь как примитивный, так и объектный тип). Этот метод принимает функцию, выполняющую преобразования, и необязательное начальное значение аккумулятора. Рассмотрим пример.const a = [1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array) => {
return accumulator * currentValue
}, 1)
console.log(a) //24
//итерация 1: 1 * 1 = 1
//итерация 2: 1 * 2 = 2
//итерация 3: 2 * 3 = 6
//итерация 4: 6 * 4 = 24
Здесь мы ищем произведение всех элементов массива, описанного с помощью литерала, задавая в качестве начального значения аккумулятора 1.
?Перебор массива с помощью метода forEach()
Метод массивов
forEach()
можно использовать для перебора значений массивов и для выполнения над ними неких действий, задаваемых передаваемой методу функцией. Например, выведем, по одному, элементы массива в консоль.const a = [1, 2, 3]
a.forEach(el => console.log(el))
//1
//2
//3
Если при переборе массива нужно остановить или прервать цикл, то при использовании
forEach()
придётся выбрасывать исключение. Поэтому если в ходе решения некоей задачи может понадобиться прерывание цикла, лучше всего выбрать какой-нибудь другой способ перебора элементов массива.?Перебор массива с использованием оператора for...of
Оператор
for...of
появился в стандарте ES6. Он позволяет перебирать итерируемые объекты (в том числе — массивы). Вот как им пользоваться.const a = [1, 2, 3]
for (let v of a) {
console.log(v)
}
//1
//2
//3
На каждой итерации цикла в переменную
v
попадает очередной элемент массива a
.?Перебор массива с использованием оператора for
Оператор
for
позволяет организовывать циклы, которые, в частности, можно использовать и для перебора (или инициализации) массивов, обращаясь к их элементам по индексам. Обычно индекс очередного элемента получают, пользуясь счётчиком цикла.const a = [1, 2, 3]
for (let i = 0; i < a.length; i += 1) {
console.log(a[i])
}
//1
//2
//3
Если, в ходе выполнения цикла, нужно пропустить его итерацию, можно воспользоваться командой
continue
. Для досрочного завершения цикла можно воспользоваться командой break
. Если в цикле, например, расположенном в некоей функции, использовать команду return
, выполнение цикла и функции завершится, а возвращённое с помощью return
значение попадёт туда, откуда была вызвана функция.?Метод @@iterator
Этот метод появился в стандарте ES6. Он позволяет получать так называемый «итератор объекта» — объект, который в данном случае позволяет организовывать перебор элементов массива. Итератор массива можно получить, воспользовавшись символом (такие символы называют «известными символами»)
Symbol.iterator
. После получения итератора можно обращаться к его методу next()
, который, при каждом его вызове, возвращает структуру данных, содержащую очередной элемент массива.const a = [1, 2, 3]
let it = a[Symbol.iterator]()
console.log(it.next().value) //1
console.log(it.next().value) //2
console.log(it.next().value) //3
Если вызвать метод
next()
после того, как будет достигнут последний элемент массива, он возвратит, в качестве значения элемента, undefined
. Объект, возвращаемый методом next()
, содержит свойства value
и done
. Свойство done
принимает значение true
до тех пор, пока не будет достигнут последний элемент массива. В нашем случае, если вызвать it.next()
в четвёртый раз, он возвратит объект { value: undefined, done: true }
, в то время как при трёх предыдущих вызовах этот объект имел вид { value: значение, done: false }
.Метод массивов
entries()
возвращает итератор, который позволяет перебирать пары ключ-значение массива.const a = [1, 2, 3]
let it = a.entries()
console.log(it.next().value) //[0, 1]
console.log(it.next().value) //[1, 2]
console.log(it.next().value) //[2, 3]
Метод
keys()
позволяет перебирать ключи массива.const a = [1, 2, 3]
let it = a.keys()
console.log(it.next().value) //0
console.log(it.next().value) //1
console.log(it.next().value) //2
?Добавление элементов в конец массива
Для добавления элементов в конец массива используют метод
push()
.a.push(4)
?Добавление элементов в начало массива
Для добавления элементов в начало массива используют метод
unshift()
.a.unshift(0)
a.unshift(-2, -1)
?Удаление элементов массива
Удалить элемент из конца массива, одновременно возвратив этот элемент, можно с помощью метода
pop()
.a.pop()
Аналогичным образом, с помощью метода
shift()
, можно удалить элемент из начала массива.a.shift()
То же самое, но уже с указанием позиции удаления элементов и их количества, делается с помощью метода
splice()
.a.splice(0, 2) // удаляет и возвращает 2 элемента из начала массива
a.splice(3, 2) // удаляет и возвращает 2 элемента, начиная с индекса 3
?Удаление элементов массива и вставка вместо них других элементов
Для того чтобы, воспользовавшись одной операцией, удалить некие элементы массива и вставить вместо них другие элементы, используется уже знакомый вам метод
splice()
.Например, здесь мы удаляем 3 элемента массива начиная с индекса 2, после чего в то же место добавляем два других элемента:
const a = [1, 2, 3, 4, 5, 6]
a.splice(2, 3, 'a', 'b')
console.log(a) //[ 1, 2, 'a', 'b', 6 ]
?Объединение нескольких массивов
Для объединения нескольких массивов можно воспользоваться методом
concat()
, возвращающим новый массив.const a = [1, 2]
const b = [3, 4]
const c = a.concat(b)
console.log(c) //[ 1, 2, 3, 4 ]
?Поиск элементов в массиве
В стандарте ES5 появился метод
indexOf()
, который возвращает индекс первого вхождения искомого элемента массива. Если элемент в массиве найти не удаётся — возвращается -1
.const a = [1, 2, 3, 4, 5, 6, 7, 5, 8]
console.log(a.indexOf(5)) //4
console.log(a.indexOf(23)) //-1
Метод
lastIndexOf()
возвращает индекс последнего вхождения элемента в массив, или, если элемент не найден, -1
.const a = [1, 2, 3, 4, 5, 6, 7, 5, 8]
console.log(a.lastIndexOf(5)) //7
console.log(a.lastIndexOf(23)) //-1
В ES6 появился метод массивов
find()
, который выполняет поиск по массиву с использованием передаваемой ему функции. Если функция возвращает true
, метод возвращает значение первого найденного элемента. Если элемент найти не удаётся, функция возвратит undefined
.Выглядеть его использование может следующим образом.
a.find(x => x.id === my_id)
Здесь в массиве, содержащем объекты, осуществляется поиск элемента, свойство
id
которого равняется заданному.Метод
findIndex()
похож на find()
, но он возвращает индекс найденного элемента или undefined
.В ES7 появился метод
includes()
, который позволяет проверить наличие некоего элемента в массиве. Он возвращает true
или false
, найдя или не найдя интересующий программиста элемент.a.includes(value)
С помощью этого метода можно проверять на наличие некоего элемента не весь массив, а лишь некоторую его часть, начинающуюся с заданного при вызове этого метода индекса. Индекс задаётся с помощью второго, необязательного, параметра этого метода.
a.includes(value, i)
?Получение фрагмента массива
Для того чтобы получить копию некоего фрагмента массива в виде нового массива, можно воспользоваться методом
slice()
. Если этот метод вызывается без аргументов, то возвращённый массив окажется полной копией исходного. Он принимает два необязательных параметра. Первый задаёт начальный индекс фрагмента, второй — конечный. Если конечный индекс не задан, то массив копируется от заданного начального индекса до конца.const a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(a.slice(4)) //[ 5, 6, 7, 8, 9 ]
console.log(a.slice(3,7)) //[ 4, 5, 6, 7 ]
?Сортировка массива
Для организации сортировки элементов массива в алфавитном порядке (
0-9A-Za-z
) используется метод sort()
без передачи ему аргументов.const a = [1, 2, 3, 10, 11]
a.sort()
console.log(a) //[ 1, 10, 11, 2, 3 ]
const b = [1, 'a', 'Z', 3, 2, 11]
b.sort()
console.log(b) //[ 1, 11, 2, 3, 'Z', 'a' ]
Этому методу можно передать функцию, задающую порядок сортировки. Функция принимает, для сравнения двух элементов, параметры
a
и b
. Она возвращает отрицательное число в том случае, если a
меньше b
по какому-либо критерию, 0 — если они равны, и положительное число — если a
больше b
. При написании подобной функции для сортировки числовых массивов она может возвратить результат вычитания a
и b
. Так, возврат результата вычисления выражения a - b
означает сортировку массива по возрастанию, возврат результата вычисления выражения b - a
даст сортировку массива по убыванию.const a = [1, 10, 3, 2, 11]
console.log(a.sort((a, b) => a - b)) //[ 1, 2, 3, 10, 11 ]
console.log(a.sort((a, b) => b - a)) //[ 11, 10, 3, 2, 1 ]
Для того чтобы обратить порядок следования элементов массива можно воспользоваться методом
reverse()
. Он, так же, как и sort()
, модифицирует массив для которого вызывается.?Получение строкового представления массива
Для получения строкового представления массива можно воспользоваться его методом
toString()
.a.toString()
Похожий результат даёт метод
join()
, вызванный без аргументов.a.join()
Ему, в качестве аргумента, можно передать разделитель элементов.
const a = [1, 10, 3, 2, 11]
console.log(a.toString()) //1,10,3,2,11
console.log(a.join()) //1,10,3,2,11
console.log(a.join(', ')) //1, 10, 3, 2, 11
?Создание копий массивов
Для создания копии массива путём копирования в новый массив значений исходного массива можно воспользоваться методом
Array.from()
. Он подходит и для создания массивов из массивоподобных объектов (из строк, например).const a = 'a string'
const b = Array.from(a)
console.log(b) //[ 'a', ' ', 's', 't', 'r', 'i', 'n', 'g' ]
Метод
Array.of()
тоже можно использовать для копирования массивов, а также для «сборки» массивов из различных элементов. Например, для копирования элементов одного массива в другой можно воспользоваться следующей конструкцией.const a = [1, 10, 3, 2, 11]
const b = Array.of(...a)
console.log(b) // [ 1, 10, 3, 2, 11 ]
Для копирования элементов массива в некое место самого этого массива используется метод
copyWithin()
. Его первый аргумент задаёт начальный индекс целевой позиции, второй — начальный индекс позиции источника элементов, а третий параметр, необязательный, указывает конечный индекс позиции источника элементов. Если его не указать, в указанное место массива будет скопировано всё, начиная от начального индекса позиции источника до конца массива.const a = [1, 2, 3, 4, 5]
a.copyWithin(0, 2)
console.log(a) //[ 3, 4, 5, 4, 5 ]
Циклы
Выше, говоря о массивах, мы уже сталкивались с некоторыми способами организации циклов. Однако циклы в JavaScript используются не только для работы с массивами, да и рассмотрели мы далеко не все их виды. Поэтому сейчас мы уделим некоторое время рассмотрению разных способов организации циклов в JavaScript и поговорим об их особенностях.
?Цикл for
Рассмотрим пример применения этого цикла.
const list = ['a', 'b', 'c']
for (let i = 0; i < list.length; i++) {
console.log(list[i]) //значения, хранящиеся в элементах циклов
console.log(i) //индексы
}
Как уже было сказано, прерывать выполнение такого цикла можно, используя команду
break
, а пропускать текущую итерацию и переходить сразу к следующей можно с помощью команды continue
.?Цикл forEach
Этот цикл мы тоже обсуждали. Приведём пример перебора массива с его помощью.
const list = ['a', 'b', 'c']
list.forEach((item, index) => {
console.log(item) //значение
console.log(index) //индекс
})
//если индексы элементов нас не интересуют, можно обойтись и без них
list.forEach(item => console.log(item))
Напомним, что для прерывания такого цикла надо выбрасывать исключение, то есть, если при использовании цикла может понадобиться прервать его, лучше выбрать какой-нибудь другой цикл.
?Цикл do...while
Это — так называемый «цикл с постусловием». Такой цикл будет выполнен как минимум один раз до проверки условия завершения цикла.
const list = ['a', 'b', 'c']
let i = 0
do {
console.log(list[i]) //значение
console.log(i) //индекс
i = i + 1
} while (i < list.length)
Его можно прерывать с использованием команды
break
, можно переходить на его следующую итерацию командой continue
.?Цикл while
Это — так называемый «цикл с предусловием». Если, на входе в цикл, условие продолжения цикла ложно, он не будет выполнен ни одного раза.
const list = ['a', 'b', 'c']
let i = 0
while (i < list.length) {
console.log(list[i]) //значение
console.log(i) //индекс
i = i + 1
}
?Цикл for...in
Этот цикл позволяет перебирать все перечислимые свойства объекта по их именам.
let object = {a: 1, b: 2, c: 'three'}
for (let property in object) {
console.log(property) //имя свойства
console.log(object[property]) //значение свойства
}
?Цикл for...of
Цикл
for...of
совмещает в себе удобство цикла forEach
и возможность прерывать его работу штатными средствами.//перебор значений
for (const value of ['a', 'b', 'c']) {
console.log(value) //значение
}
//перебор значений и получение индексов с помощью `entries()`
for (const [index, value] of ['a', 'b', 'c'].entries()) {
console.log(index) //индекс
console.log(value) //значение
}
Обратите внимание на то, что здесь, в заголовке цикла, используется ключевое слово
const
, а не, как можно было бы ожидать, let
. Если внутри блока цикла переменные не нужно переназначать, то const
нам вполне подходит.Если сравнить циклы
for...in
и for...of
, то окажется, что for...in
перебирает имена свойств, а for...of
— значения свойств.Циклы и области видимости
С циклами и с областями видимости переменных связана одна особенность JavaScript, которая может доставить разработчику некоторые проблемы. Для того чтобы с этими проблемами разобраться, поговорим о циклах, об областях видимости, и о ключевых словах
var
и let
.Рассмотрим пример.
const operations = []
for (var i = 0; i < 5; i++) {
operations.push(() => {
console.log(i)
})
}
for (const operation of operations) {
operation()
}
В цикле производится 5 итераций, на каждой из которых в массив
operations
добавляется новая функция. Эта функция выводит в консоль значение счётчика цикла — i
. После того, как функции добавлены в массив, мы этот массив перебираем и вызываем функции, являющиеся его элементами.Выполняя подобный код можно ожидать результата, показанного ниже.
0
1
2
3
4
Но на самом деле он выводит следующее.
5
5
5
5
5
Почему это так? Всё дело в том, что в качестве счётчика цикла мы используем переменную, объявленную с использованием ключевого слова
var
.Так как объявления подобных переменных поднимаются в верхнюю часть области видимости, вышеприведённый код аналогичен следующему.
var i;
const operations = []
for (i = 0; i < 5; i++) {
operations.push(() => {
console.log(i)
})
}
for (const operation of operations) {
operation()
}
В результате оказывается, что в цикле
for...of
, в котором мы перебираем массив, переменная i
всё ещё видна, она равна 5, в результате, ссылаясь на i
во всех функциях, мы выводим число 5.Как изменить поведение программы таким образом, чтобы она делала бы то, что от неё ожидается?
Самое простое решение этой проблемы заключается в использовании ключевого слова
let
. Оно, как мы уже говорили, появилось в ES6, его использование позволяет избавиться от некоторых странностей, характерных для var
.В частности, в вышеприведённом примере достаточно изменить
var
на let
и всё заработает так, как нужно.const operations = []
for (let i = 0; i < 5; i++) {
operations.push(() => {
console.log(i)
})
}
for (const operation of operations) {
operation()
}
Теперь на каждой итерации цикла каждая функция, добавленная в массив
operations
, получает собственную копию i
. Помните о том, что в данной ситуации нельзя использовать ключевое слово const
, так как значение i
в цикле меняется.Ещё один способ решения этой проблемы, который часто применялся до появления стандарта ES6, когда ключевого слова
let
ещё не было, заключается в использовании IIFE.При таком подходе значение
i
сохраняется в замыкании, а в массив попадает функция, возвращаемая IIFE и имеющая доступ к замыканию. Эту функцию можно выполнить тогда, когда в ней возникнет необходимость. Вот как это выглядит.const operations = []
for (var i = 0; i < 5; i++) {
operations.push(((j) => {
return () => console.log(j)
})(i))
}
for (const operation of operations) {
operation()
}
Итоги
Сегодня мы поговорили о массивах и о циклах в JavaScript. Тема нашего следующего материала — обработка исключений, особенности использования точки с запятой и шаблонные литералы.
Уважаемые читатели! Какими методами для работы с массивами в JavaScript вы пользуетесь чаще всего?
Комментарии (10)
Gregivy
21.11.2018 20:09-2Стоило бы сказать, что приведенные методы копирования массивов — только повторяют структуру массива и копируют только примитивные значения. Если какой-то элемент массива — объект, то в клонированном массиве будет ссылка на этот же объект, а не копия этого объекта.
WanSpi
21.11.2018 20:20+1В массиве находиться ссылка на объект, а не сам объект, по этому и копируется ссылка, так что все верно, копируется весь массив.
Gregivy
21.11.2018 21:39Так-то оно верно, но может ввести в заблуждение начинающих. Все-таки копию массива не редко делают, чтобы что-то поменять в копии, сохранив оригинал. А потом человек недоумевает, почему объект поменялся везде.
WanSpi
21.11.2018 21:47Так то оно так, но тогда уже лучше объяснить как работают сами объекты, и чем они отличаются от простых типов, а это уже тема для другой статьи, тут же описывают чисто массивы.
Juma
Меня всегда удивляют вот такие моменты:
«не рекомендуется» и всё тут, а почему непонятно. Хотя бы ссылку, почитать подробнее.mayorovp
А на кой черт он нужен-то, когда для массивов уже сто лет отдельный синтаксис есть?
Juma
Т.е. ваш ответ: «У нас так принято!»
Не спорю, чем проще синтаксис, тем лучше.
Но если нужен нестандартный вариант? (ниже)
WanSpi
Думаю из-за того, что у конструктора Array есть перегрузка, и если передать один параметр числом, то будет создан массив с этим количеством ячеек
Juma
Если дело только в этом, то варианты вроде const e = Array(1, 2, 3), туда же, так как:
Но, вообще ситуация такая, необходимы несколько массивов, но с дополнительными одинаковыми методами. Писать в Array.prototype не то. Очень удобно получается с наследованием:
WanSpi
Сама проблема заключена в конструкторе, просто перегрузите ее, только уже с той логикой, как Вам нужно, как вариант: