Объект `Array`
В этой статье рассказывается об объекте Array.
Я пошагово и понятно объясню практическое использование массивов.
YouTube Video
Объект Array
Объект Array в JavaScript — одна из важнейших структур, лежащих в основе всех видов обработки данных. От базовых операций с массивами до функций высшего порядка, полезных для эффективного преобразования данных, — существует множество функций, которые стоит знать.
Основы массивов
В JavaScript массивы — это базовая структура данных для работы с несколькими значениями одновременно. Здесь мы рассмотрим, как создавать массивы и как читать и менять их элементы на простых примерах.
1// Create arrays in different ways
2const arr1 = [1, 2, 3]; // array literal
3const arr2 = new Array(4, 5, 6); // Array constructor
4const arr3 = Array.of(7, 8, 9); // Array.of
5
6console.log("arr1 created with literal. :", arr1);
7console.log("arr2 created with constructor:", arr2);
8console.log("arr3 created with Array.of. :", arr3);
9// arr1 created with literal. : [ 1, 2, 3 ]
10// arr2 created with constructor: [ 4, 5, 6 ]
11// arr3 created with Array.of. : [ 7, 8, 9 ]
12
13// Access and modify elements
14let first = arr1[0]; // read element
15console.log("First element of arr1:", first);
16// First element of arr1: 1
17
18arr1[1] = 20; // modify element
19console.log("arr1 after modifying index 1:", arr1);
20// arr1 after modifying index 1: [ 1, 20, 3 ]
21
22const len = arr1.length; // get length
23console.log("Length of arr1:", len);
24// Length of arr1: 3
- Этот код показывает три способа создания массивов, чтение и обновление элементов по индексу, а также получение длины массива с помощью свойства
length. - Массивные литералы являются самыми распространёнными и удобочитаемыми, и чаще всего используются в повседневных ситуациях.
Добавление и удаление элементов (в конце или начале)
Массивы позволяют легко добавлять или удалять элементы в начале или конце. Эти операции также полезны при реализации таких структур, как стек или очередь.
1// Push and pop (stack-like)
2const stack = [];
3console.log("Initial stack:", stack);
4
5stack.push(1); // push 1
6console.log("After push(1):", stack);
7
8stack.push(2); // push 2
9console.log("After push(2):", stack);
10
11const last = stack.pop(); // pop -> 2
12console.log("Popped value:", last);
13console.log("Stack after pop():", stack);
14
15// Unshift and shift (queue-like)
16const queue = [];
17console.log("Initial queue:", queue);
18
19queue.push('a'); // add to end
20console.log("After push('a'):", queue);
21
22queue.unshift('start'); // add to front
23console.log("After unshift('start'):", queue);
24
25const firstItem = queue.shift(); // remove from front
26console.log("Shifted value:", firstItem);
27console.log("Queue after shift():", queue);
28
29// Initial stack: []
30// After push(1): [ 1 ]
31// After push(2): [ 1, 2 ]
32// Popped value: 2
33// Stack after pop(): [ 1 ]
34
35// Initial queue: []
36// After push('a'): [ 'a' ]
37// After unshift('start'): [ 'start', 'a' ]
38// Shifted value: start
39// Queue after shift(): [ 'a' ]
pushиpopработают с концом массива. Они идеально подходят для реализации структуры стека.unshiftиshiftработают с началом массива. Однако помните, что операции с началом массива требуют внутреннего сдвига всех индексов, поэтому такие операции затратны.
Работа с элементами в середине (splice и slice)
Обрабатывая элементы в середине массива, выбирайте между splice и slice в зависимости от того, нужно ли изменить исходный массив. Если вы хотите просто извлечь часть массива, используйте slice; если вы хотите изменить сам массив, например, вставить или удалить элементы, используйте splice.
1// slice (non-destructive)
2const nums = [0, 1, 2, 3, 4];
3console.log("Original nums:", nums);
4
5const part = nums.slice(1, 4); // returns [1, 2, 3]
6console.log("Result of nums.slice(1, 4):", part);
7console.log("nums after slice (unchanged):", nums);
8
9// splice (destructive)
10const arr = [10, 20, 30, 40];
11console.log("\nOriginal arr:", arr);
12
13// remove 1 item at index 2, insert 25 and 27
14arr.splice(2, 1, 25, 27);
15console.log("After arr.splice(2, 1, 25, 27):", arr);
16
17// Original nums: [ 0, 1, 2, 3, 4 ]
18// Result of nums.slice(1, 4): [ 1, 2, 3 ]
19// nums after slice (unchanged): [ 0, 1, 2, 3, 4 ]
20
21// Original arr: [ 10, 20, 30, 40 ]
22// After arr.splice(2, 1, 25, 27): [ 10, 20, 25, 27, 40 ]
sliceтолько извлекает элементы и не изменяет исходный массив.spliceдобавляет или удаляет элементы и изменяет сам массив, поэтому будьте особенно внимательны к его влиянию на поведение кода.
Итерация (for / for...of / forEach)
Существует несколько способов последовательной обработки массивов, и вы можете выбрать подходящий в зависимости от целей и стиля кода. Вот три типичные конструкции циклов.
1const items = ['apple', 'banana', 'cherry'];
2console.log("Items:", items);
3
4// classic for
5console.log("\n--- Classic for loop ---");
6for (let i = 0; i < items.length; i++) {
7 console.log(`Index: ${i}, Value: ${items[i]}`);
8}
9
10// for...of (recommended for values)
11console.log("\n--- for...of loop ---");
12for (const item of items) {
13 console.log(`Value: ${item}`);
14}
15
16// forEach (functional style)
17console.log("\n--- forEach loop ---");
18items.forEach((item, index) => {
19 console.log(`Index: ${index}, Value: ${item}`);
20});for— самый гибкий цикл, он позволяет оперировать индексами и точно управлять итерациями с помощьюbreakи других операторов.for...of— лаконичный способ перебора значений, отлично сочетающийся с читаемостью кода.forEachпозволяет писать код в функциональном стиле и хорошо подходит для функций с побочными эффектами, например логированию или обновлению данных каждого элемента. Однако учтите, что нельзя использоватьbreakилиcontinue, а также он не подходит для асинхронной обработки сawait.
map / filter / reduce — функции высшего порядка
map, filter и reduce — это функции высшего порядка, которые часто используются при преобразовании, фильтрации или агрегировании массивов. Поскольку вы можете явно выразить повторяющиеся процессы, ваш код становится простым и легким для понимания.
1const numbers = [1, 2, 3, 4, 5];
2console.log("Original numbers:", numbers);
3
4// map: transform each item
5const doubled = numbers.map(n => n * 2);
6console.log("\nResult of map (n * 2):", doubled);
7
8// filter: select items
9const evens = numbers.filter(n => n % 2 === 0);
10console.log("Result of filter (even numbers):", evens);
11
12// reduce: accumulate to single value
13const sum = numbers.reduce((acc, n) => acc + n, 0);
14console.log("Result of reduce (sum):", sum);
15
16// Original numbers: [ 1, 2, 3, 4, 5 ]
17// Result of map (n * 2): [ 2, 4, 6, 8, 10 ]
18// Result of filter (even numbers): [ 2, 4 ]
19// Result of reduce (sum): 15
- Эти методы позволяют сосредоточиться на сути задачи в декларативном стиле, повышая читаемость и избегая нежелательных побочных эффектов.
find / findIndex / some / every
Вот обзор методов поиска и проверки условий. Они полезны для поиска элементов, удовлетворяющих определённому условию, или для логических проверок над множеством.
1const users = [
2 { id: 1, name: 'Alice' },
3 { id: 2, name: 'Bob' },
4 { id: 3, name: 'Carol' }
5];
6
7console.log("Users:", users);
8
9// Find the first user whose name is 'Bob'
10const bob = users.find(user => user.name === 'Bob');
11console.log("\nResult of find (name === 'Bob'):", bob);
12
13// Find index of the user whose id is 3
14const indexOfId3 = users.findIndex(user => user.id === 3);
15console.log("Result of findIndex (id === 3):", indexOfId3);
16
17// Check if there exists a user with id = 2
18const hasId2 = users.some(user => user.id === 2);
19console.log("Result of some (id === 2):", hasId2);
20
21// Check if all users have a numeric id
22const allHaveNumericId = users.every(user => typeof user.id === 'number');
23console.log("Result of every (id is number):", allHaveNumericId);
24// Result of find (name === 'Bob'): { id: 2, name: 'Bob' }
25// Result of findIndex (id === 3): 2
26// Result of some (id === 2): true
27// Result of every (id is number): true
findвозвращает первый элемент, соответствующий условию.findIndexвозвращает индекс элемента, соответствующего условию.someвозвращаетtrue, если есть хотя бы один элемент, удовлетворяющий условию.everyвозвращаетtrue, если все элементы удовлетворяют условию.
Все эти методы очень полезны при работе с массивами, поэтому их правильное применение поможет сделать ваш код кратким и понятным.
Сортировка и функции сравнения
Массивы сортируются с помощью sort, но по умолчанию элементы сравниваются как строки, что может привести к неожиданным результатам при сортировке чисел.
1const nums = [10, 2, 33, 4];
2console.log("Original nums:", nums);
3
4// Default sort: compares elements as strings (not suitable for numbers)
5nums.sort();
6console.log("\nAfter default sort (string comparison):", nums);
7
8// Numeric ascending sort using a compare function
9nums.sort((a, b) => a - b);
10console.log("After numeric sort (a - b):", nums);
11
12// Sort objects by a property
13const people = [{ age: 30 }, { age: 20 }, { age: 25 }];
14console.log("\nOriginal people:", people);
15
16people.sort((a, b) => a.age - b.age);
17console.log("After sorting people by age:", people);
18
19// After default sort (string comparison): [ 10, 2, 33, 4 ]
20// After numeric sort (a - b): [ 2, 4, 10, 33 ]
21// Original people: [ { age: 30 }, { age: 20 }, { age: 25 } ]
22// After sorting people by age: [ { age: 20 }, { age: 25 }, { age: 30 } ]
- При сортировке чисел или объектов всегда указывайте функцию сравнения, чтобы сортировать в нужном порядке.
- В функции сравнения отрицательное значение возвращает
aпередb, положительное —bпередa, а 0 оставляет их порядок неизменным.
Копирование массива и неизменяемость
При копировании массивов важно понимать разницу между «копированием ссылки» и «поверхностным копированием». Особенно важно помнить, что если в массиве есть объекты, при поверхностном копировании внутренние объекты будут общими.
Копирование ссылки
Когда вы присваиваете массив другой переменной, сам массив не дублируется; вместо этого копируется «ссылка», указывающая на тот же массив.
1const a = [1, 2, 3];
2console.log("Original a:", a);
3
4// Reference copy; modifying b also affects a
5const b = a;
6console.log("\nReference copy b = a:", b);
7// Reference copy b = a: [ 1, 2, 3 ]
8
9b[0] = 100;
10console.log("After modifying b[0] = 100:");
11console.log("a:", a); // a: [ 100, 2, 3 ] (affected)
12console.log("b:", b); // b: [ 100, 2, 3 ]
- При копировании ссылки, если вы изменяете содержимое массива с помощью скопированной переменной, эти изменения также отразятся на оригинальной переменной, ссылающейся на тот же массив.
Поверхностное копирование
Использование slice() или синтаксиса расширения (spread) создает «поверхностную копию», так как дублируются только значения; оригинальный и скопированный массивы рассматриваются как отдельные сущности.
1const a = [1, 2, 3];
2console.log("Original a:", a);
3
4// Shallow copy (new array)
5const b = a.slice();
6console.log("\nShallow copy using slice():", b);
7// Shallow copy using slice(): [ 100, 2, 3 ]
8
9const c = [...a];
10console.log("Shallow copy using spread [...a]:", c);
11// Shallow copy using spread [...a]: [ 100, 2, 3 ]
12
13// Modifying c or d does NOT affect a
14b[1] = 200;
15c[2] = 300;
16console.log("\nAfter modifying b[1] = 200 and c[2] = 300:");
17console.log("a:", a); // [ 100, 2, 3 ]
18console.log("b:", b); // [ 100, 200, 3 ]
19console.log("c:", c); // [ 100, 2, 300 ]
- Этот код демонстрирует, что поверхностная копия массива, созданная с помощью
slice()или синтаксиса расширения, не влияет на оригинальный массив.
Поверхностное копирование и неизменяемость
Даже если вы дублируете массив с помощью «поверхностной копии», может произойти непреднамеренное совместное использование, если внутри массива находятся объекты.
1// Shallow copy doesn't clone inner objects
2const nested = [{ x: 1 }, { x: 2 }];
3console.log("\nOriginal nested:", nested);
4// Original nested: [ { x: 1 }, { x: 2 } ]
5
6const shallow = nested.slice();
7console.log("Shallow copy of nested:", shallow);
8// Shallow copy of nested: [ { x: 1 }, { x: 2 } ]
9
10// Changing inner object affects both arrays
11shallow[0].x = 99;
12console.log("\nAfter shallow[0].x = 99:");
13console.log("nested:", nested);
14console.log("shallow:", shallow);
15// nested: [ { x: 99 }, { x: 2 } ]
16// shallow: [ { x: 99 }, { x: 2 } ]
- Этот код показывает, что при поверхностном копировании внутренние объекты являются общими, поэтому изменение этих объектов влияет как на оригинальный массив, так и на его копию.
- Если вам нужны независимые данные, используйте «глубокое копирование», например с помощью
structuredClone()или преобразования в JSON.
Полезные вспомогательные методы
Ниже перечислены утилитарные методы, часто используемые при работе с массивами. Используя их по назначению, вы сможете писать короткий и читаемый код.
includes
Метод includes проверяет, содержится ли конкретное значение в массиве.
1// includes: check if array contains a value
2const letters = ['a', 'b', 'c'];
3const hasB = letters.includes('b');
4console.log("letters:", letters); // [ 'a', 'b', 'c' ]
5console.log("letters.includes('b'):", hasB); // true
- В этом коде метод
includesиспользуется для краткого определения наличия указанного значения в массиве.
concat
Метод concat возвращает новый массив, добавляя указанный массив или значения в конец, при этом исходный массив остается без изменений.
1// concat: merge arrays without mutation
2const a1 = [1, 2];
3const a2 = [3, 4];
4const combined = a1.concat(a2);
5console.log("a1.concat(a2):", combined); // [ 1, 2, 3, 4 ]
6console.log("a1(unchanged):", a1); // [ 1, 2 ]
- Этот код показывает, что
concatне разрушающий, что позволяет создавать новый массив и сохранять оригинальный.
flat
С помощью метода flat вы можете «расплющить» вложенные массивы.
1// flat and flatMap: flatten arrays or map + flatten in one step
2const nested = [1, [2, [3]]];
3console.log("nested:", nested); // nested: [ 1, [ 2, [ 3 ] ] ]
4console.log("nested.flat():", nested.flat()); // default depth = 1
5// nested.flat(): [ 1, 2, [ 3 ] ]
- Этот код демонстрирует результат расплющивания массива на один уровень.
- Поскольку
flatпозволяет указывать глубину, вы можете гибко управлять уровнем вложенности по необходимости.
flatMap
Метод flatMap применяет преобразование к каждому элементу, а затем автоматически расплющивает результаты в одномерный массив.
1// flat and flatMap: flatten arrays or map + flatten in one step
2const words = ['hello world', 'hi'];
3console.log("words:", words); // [ 'hello world', 'hi' ]
4
5const splitWords = words.flatMap(w => w.split(' '));
6console.log("words.flatMap(w => w.split(' ')):", splitWords);
7// words.flatMap(w => w.split(' ')): [ 'hello', 'world', 'hi' ]
- Этот код показывает пример, где каждая строка в массиве разделяется по пробелам, а затем результаты объединяются и расплющиваются в один массив.
join
Метод join создает строку, объединяя элементы массива с помощью указанного разделителя.
1// join: combine elements into a string with a separator
2const csv = ['a', 'b', 'c'].join(',');
3console.log("['a', 'b', 'c'].join(','):", csv); // a,b,c
- В этом коде метод
joinиспользуется для преобразования массива в строку, разделённую запятыми.
Типичные ошибки
Операции с массивами могут показаться простыми на первый взгляд, но есть несколько моментов, приводящих к нежелательному поведению. Многие ловушки легко не заметить в повседневной работе с массивами, поэтому запомните следующие моменты — это повысит надежность вашего кода.
sort()дляArrayпо умолчанию сортирует элементы как строки. Чтобы корректно сортировать числа, всегда указывайте функцию сравнения.- Копирование массивов (через
slice, spread-оператор и т.д.) создает поверхностную копию. Если ваши массивы содержат объекты, будьте осторожны — исходные данные могут измениться непреднамеренно. splice— это деструктивный метод, который напрямую изменяет массив, тогда какslice— недеструктивный метод, не изменяющий исходный массив. Важно использовать их в соответствии с вашими задачами.forEachне подходит для асинхронных обработок с использованиемawaitв цикле. Если вам нужно гарантированно последовательно выполнять асинхронную обработку, рекомендуется использоватьfor...of.
Практический пример
Ниже приведен пример, где методы массивов объединяются для «подсчета общего возраста пользователей, выбора тех, кому 30 лет и старше, и создания списка их имен».
1const users = [
2 { name: 'Alice', age: 28 },
3 { name: 'Bob', age: 34 },
4 { name: 'Carol', age: 41 },
5 { name: 'Dave', age: 19 }
6];
7
8console.log("Users:", users);
9
10// sum ages
11const totalAge = users.reduce((acc, u) => acc + u.age, 0);
12console.log("\nTotal age of all users:", totalAge);
13// Total age of all users: 122
14
15// filter and map names of users 30 and older
16const namesOver30 = users
17 .filter(u => u.age >= 30)
18 .map(u => u.name);
19
20console.log("Users aged 30 or older (names):", namesOver30);
21// Users aged 30 or older (names): [ 'Bob', 'Carol' ]
- С помощью цепочки
reduce,filterиmapможно просто описать агрегацию, фильтрацию по условию и преобразование данных. - Подобный «конвейер обработки данных» очень читаем и, благодаря малому количеству побочных эффектов, часто используется в реальных приложениях.
Резюме
В JavaScript даже базовые операции с массивами имеют широкий спектр применения, а использование функций высшего порядка делает код более компактным и выразительным. Тонкостей здесь немало, но, освоив правильное использование каждого метода, вы значительно упростите обработку данных.
Вы можете следовать этой статье, используя Visual Studio Code на нашем YouTube-канале. Пожалуйста, также посмотрите наш YouTube-канал.