Массив — это самая распространенная структура данных во всех языках программирования. В этом уроке мы узнаем, что такое массив и объект, а также подробно разберем встроенные методы для работы с массивами в JavaScript.
Массив — это структура данных, которая содержит упорядоченный набор элементов и предоставляет произвольный доступ к своим элементам.
Рассмотрим пример массива:
У нас есть массив a
, и у него длина n
. Он проиндексирован — каждое его значение хранится под определенным номером. Они идут по порядку и начинаются с нуля. Первый индекс всегда нуль — это стандарт.
Последний индекс будет n-1
, так как количество элементов всегда на единицу больше, чем количество индексов.
Чтобы обратиться к элементам, нужно указать его имя и в квадратных скобках — индекс.
На изображении видно, что элементы идут подряд — это верное представление с точки зрения работы с массивом, но его реализация может отличаться. То, как физически массив хранится в памяти, зависит от языка программирования, там порядок набора элементов может быть другой. Но эти детали скрыты от разработчика, поэтому мы всегда представляем массив в виде элементов, которые идут друг за другом.
В JavaScript пустой массив создается с помощью квадратных скобок:
const arr1 = [];
Если массив инициализируется какими-то значениями, то их нужно указать в квадратных скобках через запятую:
const arr2 = [1, 'string', {}, [3, NaN]];
В JavaScript и других динамических языках нет ограничений на типы этих значений. Здесь можно смешивать любые типы. При этом элементами массива могут быть сами массивы.
Чтобы обратиться к элементу, нужно после имени массива написать квадратные скобки и индекс:
arr2[0]; // 1
arr2[3]; //[3, NaN]
В итоге мы получим значения, с которыми можем работать дальше.
Также мы можем запросить элемент, которого нет в массиве. Например, используем индекс 4
:
arr2[4]; // undefined
У нас нет элемента с таким индексом. В результате мы не получим ошибку. В этом случае в JavaScript мы получаем undefined
.
В работе с массивами в JavaScript есть особенность:
const arr2 = [1, 'string', {}, [3, NaN]];
typeof arr2; // object
С помощью операции typeof
мы смотрим, какой тип у массива. Получаем object
.
Дело в том, что когда мы создаем массив, то создается объект. Квадратные скобки заменяют такую конструкцию:
new Array(1, 'string', {}, [3, NaN]);
Фактически мы создаем новый элемент с типом array
, в котором через запятую перечисляем элементы внутри него, и он является объектом.
Array
— это подтип объекта, который обладает определенными свойствами. Мы можем обращаться к нему, как к другим объектам. Например, array
определяет свойство length
, которое возвращает длину этого массива:
// length возвращает длину массива
arr2.length; // 4
arr2['length']; // 4
Свойство length
обновляется автоматически. Например, если мы что-то добавляем или удаляем, то оно будет меняться в зависимости от того, что мы делаем.
При этом квадратные скобки при обращении к массиву не являются специфичными для массива. В примере мы обратились к свойству через квадратные скобки arr2['length']
— это работает. Также к свойствам можно обращаться через точку arr2.length
. Оба варианта делают одно и то же.
Теперь представим, как бы мы определили массив в терминах объекта. Мы сделали объект, у которого ключи являются числовыми — как индексы:
const obj = { 0: 1, 1: 'string', 2: {}, 3: [3, NaN] }
console.log(obj[0]); // 1
Этот объект по своему виду соответствует массиву, который разобрали выше. Используются те же значения.
Если обратиться к нему также через скобки и поставить туда индекс 0, то мы получим единицу. Это работает практически как массив. Массив синтаксически ничего не добавляет кроме конструкции []
, которая заменяет new Array()
. Все остальное — это часть типа данных object
в JavaScript. Из-за того, что они смешиваются в одну сущность, это добавляет некоторые неожиданные эффекты, с которыми мы познакомимся позже.
В большинстве языков программирования массивы так не пересекаются с объектами, так как являются разными типами данных. Например, в PHP вообще нет массивов, а есть только объекты или так называемые ассоциативные массивы, с которыми мы тоже познакомимся попозже.
Рассмотрим манипуляции, которые можно делать с массивом в JavaScript:
Разберем каждый метод подробнее.
Мы можем переустановить значение любого элемента в массиве. Для этого обращаемся к соответствующему индексу и приравниваем его:
const arr = [1, 'string', [3, 2]];
arr[0] = 'hello';
arr[0]; // hello
arr.length; // 3
Синтаксис такой же, как и с константами переменными. Мы просто присваиваем новое значение в индекс массива arr[0] = 'hello'
. Если мы потом выведем его, то увидим новое значение hello
. При этом длина массива не изменилась, так как мы просто заменили значение под индексом 0.
В данном примере используется const
, а не let
. Мы сохраняем в константе массив, и константа не меняется. Мы всегда работаем через константу с одним и тем же массивом.
При этом JavaScript не гарантирует, что сам массив не будет меняться. Мы можем изменить его добавлением, удалением или заменой каких-то элементов. Но сам массив остается тот же. Поэтому, если у нас не происходит замены всего массива на новый, то нужно всегда использовать const
. Это применимо и к другим типам данных.
Напомним, что const
пишется один раз при создании массива.
Также мы можем делать произвольное добавление. Нам необязательно добавлять значение в тот индекс, который уже существует. Например:
const arr = [1, 'string', {}];
arr[5] = 'world' ;
Здесь мы добавляем значение не в последний индекс. Сейчас их у нас три, а мы добавляем значение world
в индекс 5.
В этом случае мы не получим ошибку. Если распечатать массив, то можно увидеть следующее:
console.log(arr);
// [1, 'string', {}, , , 'world' ]
Если мы выведем длину массива, то она будет равна 6:
arr.length; // 6
Получается, что пустые места — это реальные элементы — чем-то заполнились. Если мы попытаемся вывести на экран значение под индексом 4, то получим undefined
:
arr[4]; // undefined
Сам элемент существует, но его значение равно undefined
. Если мы обращаемся к индексу, которого не существует, то тоже получаем undefined
.
Получается, что в одном случае индекс с элементом есть, а в другом случае индекса нет. Но мы в любом случае получаем undefined
.
Иногда разница в этих значениях есть, и ее нужно учитывать. А иногда ее нет. Главное помнить, что undefined
не всегда означает, что этого индекса не существует. Там действительно может лежать значение undefined
.
Чтобы добавить что-то в массив, используют метод push
:
const arr = [1, 'string', {}];
arr.push(10);
push
берет последний индекс, прибавляет к нему единицу и размещает по этому индексу то значение, которое мы передаем в push
.
Если распечатать его, то мы получим 10:
arr[3]; // 10
// длина массива увеличивается на единицу:
arr.length; // 4
Это стандартный подход при работе с массивом.
При работе с массивами есть механизм удаления элемента, но он работает не так, как ожидается:
// Создаем массив
const arr = [1, 'string', {}];
// Используем специальную конструкцию delete, в которой передаем элемент массива с индексом
delete arr[1];
// Длина массива не изменилась
arr.length; // 3
// Элемент стал undefined
arr[1]; // undefined
Вместо удаления элемента происходит его очистка, а значением элемента становится undefined
.
Если распечатать, то увидим пропущенное значение:
console.log(arr);
// [ 1, , {} ]
Удалять что-то из массива — это плохая практика, которая может приводить к проблемам. О них мы поговорим позже. Поэтому так не стоит делать.
Если вам нужно получить массив, в котором отсутствуют какие-то элементы, то нужно применять фильтрацию. По факту нужно сформировать новый массив и добавить в него только те элементы из оригинального массива, которые нам нужны.
Чтобы добавить или удалить элементы в начало или конец массива, используются методы shift
, unshift
, pop
и push
. Эта группа методов позволяет работать с массивом как с очередью — метод доступа FIFO, или стеком — метод доступа LIFO. Рассмотрим это на примере:
// Инициализируем демонстрационный массив
const planets = ['Mercury', 'Venus', 'Earth', 'Mars'];
Очередь является упорядоченным набором данных, организованным по принципу FIFO — first in — first out. То есть элементы всегда добавляются в конец очереди, а удаляются из ее начала:
// push добавляет один или несколько элементов в конец массива
// и возвращает длину измененного (мутированного) массива
// (если точнее, то метод возвращает обновленное свойство length массива,
// которое является значением последнего индекса, увеличенным на единицу)
planets.push('Jupiter'); // 5
planets.push('Saturn', 'Uranus'); // 7
console.log(planets); // => [ 'Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus' ]
// shift удаляет первый элемент массива и возвращает его значение
planets.shift(); // удалили элемент из начала массива, возвращаемое значение: 'Mercury'
planets.shift(); // еще раз удалили элемент из начала массива, возвращаемое значение: 'Venus'
console.log(planets); // => [ 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus' ]
Стек является упорядоченным набором данных, организованным по принципу LIFO — last in — first out. То есть элементы добавляются и удаляются всегда из конца такой коллекции:
// Добавим элемент в конец массива уже известным нам способом
planets.push('Neptune'); // 6
console.log(planets); // => [ 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune' ]
// pop удаляет последний элемент массива и возвращает его значение
planets.pop(); // 'Neptune'
planets.pop(); // 'Uranus'
console.log(planets); // => [ 'Earth', 'Mars', 'Jupiter', 'Saturn' ]
Классический способ итерации для императивных языков — это цикл. В JavaScript много циклов, для работы с массивами часто применяется for…of
:
const iterable = [10, 20, 30];
for (const value of iterable) {
// В value каждый раз оказывается текущее значение по порядку
console.log(value);
}
// 10
// 20
// 30
Канонический способ работы с коллекциями в JavaScript — функции высшего порядка.
Представим, что у нас есть массив numbers
:
const numbers = [1, 4, 9];
Мы хотим выполнить с ним стандартные операции: map
, filter
, reduce
и reduceRight
:
Пример работы с map
:
const roots = numbers.map((value) => Math.sqrt(value));
// Передаем в map анонимную функцию, внутри которой вызываем `Math.sqrt()` и передаем в него текущее значение из списка `value`
console.log(roots); // [1, 2, 3]
То же самое:
const roots = numbers.map(Math.sqrt);
console.log(roots); // [1, 2, 3]
В JavaScript все функции являются объектами первого рода, и мы можем их передавать как аргументы. Во втором примере мы вызываем метод map
на массиве numbers
и передаем туда саму функцию Math.sqrt()
, так как она принимает тот же параметр, что и наша функция-обертка.
В итоге получаются корни — roots
. Если их распечатать, у нас появляется 1, 2, 3.
Следующий пример — это фильтрация:
// Передаем анонимную функцию, которая выбирает элементы больше 3
const filtered = numbers.filter(n => n > 3);
console.log(filtered); // [4, 9]
Если мы распечатаем получившийся массив, то получим 4,9, так как у нас единица меньше 3, она сюда не попадает.
Наиболее комплексный пример из функций высшего порядка — это reduce
:
const sum = numbers.reduce(
// reduce принимает на вход два параметра. Первый — анонимная функция
(acc, value, index, arr) => acc + value,
// Второй параметр — начальный счетчик. В данном случае мы находим сумму, поэтому начальный счетчик равен нулю
0
);
console.log(sum); // 14
В итоге возвращаем тело функции в преобразованный новый аккумулятор и считаем сумму элементов массива. Если его распечатать, то получаем 14.
У анонимной функции достаточно много параметров. В JavaScript это сделано для удобства. Чаще всего используются только первые два параметра. Список параметров:
acc
— аккумуляторvalue
— значение текущего, которое обрабатываетсяindex
— индекс, что может быть полезно в определенных случаяхarr
— сам массивПример работы с reduceRight
:
// reduceRight — зеркальное отражение reduce, осуществляет свертку, обрабатывая элементы справа налево
const planets = ['Меркурий', 'Венера', 'Земля', 'Марс'];
const closer = planets.reduceRight((acc, planet) => `${acc} ${planet}`, 'всё ближе к Солнцу:'); // 'всё ближе к Солнцу: Марс Земля Венера Меркурий'
const further = planets.reduce((acc, planet) => `${acc} ${planet}`, 'всё дальше от Солнца:'); // 'всё дальше от Солнца: Меркурий Венера Земля Марс'
Чтобы строки превратить в массив, а также сделать обратное, используют split
и join
:
const planets = 'Mercury,Venus,Earth,Mars';
console.log(planets.split(',')); // => [ 'Mercury', 'Venus', 'Earth', 'Mars' ]
console.log(planets.split(',').join('-')); // => 'Mercury-Venus-Earth-Mars'
Поиск в массиве и проверка соблюдения условий элементами массива производится с помощью find
, findIndex
, indexOf
, lastIndexOf
, some
, every
, includes
и так далее. Например:
// includes определяет наличие элемента в массиве, возвращая true/false
const planets = ['Mercury', 'Venus', 'Earth', 'Mars'];
planets.includes('Earth'); // true
planets.includes('Earth', 3); // ищет с третьей индексной позиции, поэтому false
planets.includes('Saturn'); // false
Сортировка массива происходит с помощью sort
, reverse
.
const planets = ['Mercury', 'Venus', 'Earth', 'Mars']
planets.sort(); // ['Earth', 'Mars', 'Mercury', 'Venus']
planets.reverse(); // ['Venus', 'Mercury', 'Mars', 'Earth']
Разберем еще один метод flat()
. Он делает содержимое вложенных элементов-массивов исходного массива элементами самого исходного массива. Он словно выравнивает массив. Посмотрим на код:
const arr = [1, 2, [1.1, 1.2, [2.1, [3.1, 3.2]]], 3];
// метод не изменяет исходный массив, а возвращает новый, поэтому результат присваиваем в новую переменную
const flattenArr = arr.flat();
console.log(flattenArr); // => [1, 2, 1.1, 1.2, [2.1, [3.1, 3.2]], 3]
Как видно из предыдущего примера, нам удалось выровнять все элементы на один уровень ниже лежащих массивов. При этом элементы более глубоко лежащих массивов остались невыровненными.
Можно управлять уровнем глубины выравнивания, который указывается в параметре метода:
console.log(arr.flat(1)); // => [1, 2, 1.1, 1.2, [2.1, [3.1, 3.2]], 3]
console.log(arr.flat(2)); // => [1, 2, 1.1, 1.2, 2.1, [3.1, 3.2], 3]
console.log(arr.flat(3)); // => [1, 2, 1.1, 1.2, 2.1, 3.1, 3.2, 3]
Для полного выравнивания нужно передать в параметр `Infinity`:
```javascript
const flattenDeepArr = arr.flat(Infinity);
console.log(flattenDeepArr); // => [1, 2, 1.1, 1.2, 2.1, 3.1, 3.2, 3]
lodash
Существуют библиотеки, которые позволяют пользоваться множеством готовых функций. Одной из наиболее актуальных на текущей момент Javascript-библиотек является lodash
. В ней реализованы различные функции по работе с массивами, коллекциями, строками и другими объектами языка.
Эта библиотека предоставляет большое количество функций высшего порядка, которые поддерживают функциональный стиль программирования. Подробнее ознакомиться со всеми возможностями lodash
можно на сайте библиотеки.
Изменить объект в императивном стиле, когда можно писать код в неизменяемом — часто является не самым удачным решением. Это ведет к изменению состояния программы и усложняет ее. Поэтому, когда изучаете методы, всегда обращайте внимание, изменяют ли они исходный массив или нет.
Например, большинство примененных к массиву функций высшего порядка результатом возвращают новый массив. При этом они оставляют исходный нетронутым. Метод sort
, как сказано в документации, необратимым образом модифицирует исходный массив. Поэтому такую сортировку лучше сделать на копии массива, если нам не нужно, чтобы исходный массив менялся.
Возьмем для примера типичную ситуацию, когда необходимо добавить новый элемент в массив. Рассмотрим, как справиться с этой задачей на примeре изменяющего метода push
и неизменяющего concat
:
push
const colours = ['red', 'orange', 'yellow'];
colours.push('green'); // возвращаемое значение: 4
console.log(colours); // теперь colours - [ 'red', 'orange', 'yellow', 'green' ]
push
добавляет один или более элементов в конец массива, тем самым изменяет его, и возвращает новую длину массива.
concat
const colours = ['red', 'orange', 'yellow'];
const myFavColours = colours.concat('green');
console.log(colours);
// colours остался неизменен — [ 'red', 'orange', 'yellow' ]
console.log(myFavColours);
// новый массив myFavColours — [ 'red', 'orange', 'yellow', 'green' ]
// аргументом можно передать и массив значений
console.log(myFavColours.concat(myFavColours));
// => [ 'red', 'orange', 'yellow', 'green', 'red', 'orange', 'yellow', 'green' ]
concat
возвращает новый массив, который содержит элементы исходного массива и элементы, переданные в качестве аргументов.
В этом уроке мы узнали, что такое массив. Это структура данных, которая содержит упорядоченный набор элементов и предоставляет произвольный доступ к своим элементам. Также мы рассмотрели манипуляции, которые можно делать с массивом в JavaScript. К ним относятся:
Вам ответят команда поддержки Хекслета или другие студенты.
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
Зарегистрируйтесь или войдите в свой аккаунт