Массив — это ссылка
Представьте дом и бумажку с его адресом. Если переписать адрес на вторую бумажку, домов не станет два: обе бумажки ведут к одному и тому же дому. Если по первой бумажке прийти и переставить мебель, то и гость со второй бумажкой увидит её на новом месте.
С числом всё иначе: присваивание b = a копирует само значение. Для массива оно работает как вторая бумажка: после b.push(3) длина a тоже выросла. А два массива с одинаковыми элементами не равны друг другу:
const a = [1, 2];
const b = a;
b.push(3);
console.log(a.length); // 3
console.log(a === b); // true
console.log([1, 2] === [1, 2]); // falseДавайте разберём по строкам:
const a = [1, 2]создаёт массив, иaуказывает на него.const b = aне создаёт второй массив:b— второе имя того же массива.b.push(3)добавляет элемент в этот единственный массив.a.lengthпечатает3: изменение черезbвидно черезa.a === bдаётtrue: оба имени указывают на один массив.[1, 2] === [1, 2]даётfalse, хотя элементы одинаковые.
Два имени одного массива
Переменная хранит не сам массив, а ссылку на него — указание, где массив лежит. Похоже на адрес дома на бумажке: b = a переписывает адрес, а дом остаётся один. В отличие от адреса, ссылку нельзя увидеть или напечатать.
Присваивание b = a копирует ссылку, и оба имени указывают на один и тот же массив. Изменение через одно имя видно через другое. const здесь не помогает: константа хранит ссылку, а не содержимое массива.
Так же массив передаётся в функцию. Параметр функции получает ссылку на тот же массив, поэтому push внутри функции изменит массив снаружи. Этот вопрос часто задают на собеседовании: «Что будет с массивом, если изменить его внутри функции?»
Нажмите «Следующий шаг» и следите за a и b: после каждого изменения у них одинаковое содержимое. Массив c — отдельная копия: его изменения не видны через a. Запись [...a], которая делает копию, разберём чуть ниже.
1function addItem(list, item) {2 list.push(item);3}4 5const a = [1, 2];6const b = a;7b.push(3);8console.log(a.length);9 10const c = [...a];11c.push(4);12console.log(a.length, c.length);13 14addItem(a, 5);15console.log(b.length);16console.log(a === b, a === c);
Запись [1, 2] создаёт массив, а a получает ссылку на него.
Сравнение массивов
Оператор === для массивов не сравнивает элементы. Он проверяет, один ли это массив, то есть одинаковые ли ссылки. Каждая запись [1, 2] создаёт новый массив, поэтому [1, 2] === [1, 2] даёт false.
Копия через три точки
Отдельную копию создаёт запись [...a]. Три точки перед массивом называют синтаксисом расширения: они раскладывают массив на отдельные элементы. Такую же копию даёт вызов slice() без аргументов.
const a = [3, 8, 1];
const copy = [...a];
copy.push(9);
console.log(a.length, copy.length); // 3 4
console.log(copy === a); // false
console.log([0, ...a].join(" ")); // 0 3 8 1
console.log(Math.max(...a)); // 8В новом массиве
В записи [...a] элементы попадают в новый массив, и у копии своя ссылка. Поэтому copy.push(9) не меняет a, а copy === a даёт false. Рядом с тремя точками можно писать и другие элементы: [0, ...a] — это 0 и за ним все элементы a.
В вызове функции
Раскладывать массив тремя точками можно и в вызове функции. Math.max возвращает наибольший из своих аргументов. Вызов Math.max(...a) передаёт три числа отдельными аргументами, а Math.max(a) получил бы один массив и вернул бы NaN.
Частая ошибка
Массив проверили на пустоту сравнением с []:
const list = [];
if (list === []) { // ✗
console.log("empty");
}Ошибки нет, но программа ничего не печатает. Справа от === стоит новый пустой массив, а не list, и условие всегда ложно. Пустоту проверяют по длине: list.length === 0.
Главное
- Переменная хранит ссылку на массив, и
b = aдаёт тому же массиву второе имя. - Функция получает ссылку на тот же массив и может его изменить.
===сравнивает ссылки, а не элементы, поэтому[1, 2] === [1, 2]даётfalse.[...a]делает отдельную копию массива с новой ссылкой.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.