Массивы: основы
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Объект удобен, когда у каждого значения есть имя. Но списку покупок имена не нужны — нужен порядок. Для упорядоченных данных в JavaScript есть массив: пронумерованный ряд ячеек, где важно, что идёт первым, а что третьим.
Массив создают квадратными скобками: внутри перечисляют элементы через запятую. Пустой массив — просто [], в него потом можно что-нибудь положить.
Массив не требует, чтобы элементы были одного типа. Числа, строки, логические значения, другие массивы, функции — всё это спокойно лежит рядом. Вложенный массив достают двумя парами скобок подряд.
Есть второй способ создания — new Array(). В реальном коде его почти не встретишь, и вот почему: с одним числовым аргументом он создаёт не массив из одного числа, а массив заданной длины из пустых ячеек. Двусмысленность на ровном месте — пишите квадратные скобки.
Элементы нумеруются с нуля: первый — arr[0], второй — arr[1]. Поэтому последний элемент всегда arr[arr.length - 1]. Привыкайте к этой формуле, она встретится вам сотни раз; есть и короткая запись arr.at(-1), которая считает с конца.
Обращение к несуществующему индексу не роняет программу — возвращается undefined. Это удобно и одновременно опасно: опечатка в индексе не вызовет ошибку, а тихо передаст undefined дальше по коду. Запись по индексу работает так же просто: присвоили — заменили.
length — не количество элементов, а наибольший индекс плюс один. Пока массив заполнен подряд, разницы не видно, и об этом легко забыть. Но стоит присвоить далёкий индекс — и длина прыгает через пустоту.
Второе, что важно знать: length доступно для записи. Уменьшили — массив обрезался, и вернуть отрезанное увеличением длины уже нельзя, на месте элементов будет пустота. Отсюда короткий приём очистки: arr.length = 0.
Четыре метода закрывают почти всю повседневную работу со списком. Два работают с концом массива: push добавляет, pop забирает. Два — с началом: unshift добавляет, shift забирает. Все четыре меняют исходный массив, а не возвращают копию.
| Метод | Что делает | Что возвращает |
|---|---|---|
| push(...) | добавляет в конец | новую длину |
| pop() | удаляет последний | удалённый элемент |
| unshift(...) | добавляет в начало | новую длину |
| shift() | удаляет первый | удалённый элемент |
push и unshift принимают сколько угодно значений сразу: arr.push(1, 2, 3) добавит три элемента за один вызов. Комбинацией push + shift получается очередь: кто раньше встал, тот раньше вышел.
push и pop трогают только последнюю ячейку. shift и unshift сдвигают весь остаток массива и перенумеровывают его, поэтому на списке из миллиона элементов разница в скорости уже заметна.
Под капотом массив — обычный объект с числовыми ключами и особым свойством length. Отсюда все странности: typeof для массива возвращает "object", и отличить массив от объекта приходится отдельной функцией Array.isArray.
Главное следствие важнее: переменная хранит не сам массив, а ссылку на него. Присвоили другой переменной — обе смотрят на один и тот же массив в памяти, и изменение через одну видно через другую. Это не баг, а то же поведение, что у объектов.
Раз массив — объект, ему можно добавить нечисловое свойство. Делать так не стоит: length его не заметит, перебор по индексам пропустит, а движок потеряет внутренние оптимизации, ради которых массивы и существуют.
Если присвоить далёкий индекс или удалить элемент через delete, в массиве появятся дыры — ячейки, которых физически нет. Внешне они выглядят как undefined, но это не одно и то же: часть методов дыры пропускает, а движок переводит такой массив в медленный режим хранения. Чтобы удалить элемент по-настоящему, используют splice, а не delete.
Классический способ — цикл по индексам: он даёт и номер, и значение. Если номер не нужен, короче и читаемее for..of — он идёт прямо по значениям. Третий вариант, forEach, вызывает вашу функцию для каждого элемента и передаёт в неё значение и индекс.
А вот for..in для массивов не применяют. Он перебирает ключи объекта: индексы придут строками, в перебор попадут добавленные вручную свойства, а работает он заметно медленнее. Для массива это лишний риск без единой выгоды.
- Массив — упорядоченный список: элементы нумеруются с нуля, последний доступен как arr[arr.length - 1] или arr.at(-1).
- length — это наибольший индекс плюс один, а не количество элементов; свойству можно присвоить значение, и массив обрежется без возможности вернуть удалённое.
- push и pop работают с концом массива, unshift и shift — с началом; вторая пара сдвигает весь остаток и потому медленнее.
- Массив — это объект: typeof возвращает "object", проверяют через Array.isArray, а переменная хранит ссылку, а не копию.
- Перебирают массив циклом по индексам, for..of или forEach; for..in для массивов не используют, а дыр от delete и далёких индексов избегают.
1/3Массив создали как [1, 2, 3], затем выполнили arr[7] = 8. Чему теперь равно arr.length?
2/3Какая пара методов работает с началом массива?
3/3let a = [1, 2]; let b = a; b.push(3); console.log(a.length) — что выведется?
Сумма цен
Дан массив цен. Посчитайте их сумму и выведите её. Сделайте это двумя способами: через for..of и через цикл по индексам — результат должен совпасть.
С for..of код читается почти как фраза: «для каждой цены из массива прибавь её к сумме».
Через индексы получится то же самое, только длиннее — и здесь легко ошибиться в границе цикла:
Условие именно i < prices.length, а не <=: последний индекс на единицу меньше длины. С <= на последнем шаге вы получите undefined, и сумма превратится в NaN.
Обрезать список без slice
В списке шесть дел, а показать нужно только первые три. Условие: не использовать slice и не создавать новый массив — задача решается одной строкой.
Свойству length можно присвоить значение, и всё, что оказалось за новой границей, отбрасывается:
Это единственный способ укоротить массив «на месте». slice(0, 3) вернул бы новый массив, а исходный остался бы шестиэлементным. И помните про необратимость: обратное присваивание todo.length = 6 вернёт длину, но не дела — на их местах будут пустые ячейки.