Переменные (и константы) в JavaScript могут хранить два вида данных: примитивные и ссылочные. К примитивным относятся все примитивные типы: числа, строки, булеан и так далее. К ссылочным – объекты. Объекты, в общем смысле, изучаются только в следующем курсе, но массив внутри – это тоже объект:
typeof []; // 'object'
В чем разница между ссылочными и примитивными типами данных и почему об этом нужно знать?
С точки зрения прикладного программиста, разница проявляется при изменении данных, их передаче и возврате из функций. Мы уже знаем, что массив можно менять, даже если он записан в константу. Здесь как раз и проявляется ссылочная природа. Константа хранит ссылку на массив, а не сам массив, и эта ссылка не меняется. А вот массив поменяться может. С примитивными типами такой трюк не пройдет.
Другой способ убедиться в том, что массивы являются ссылками – создать несколько констант, содержащих один массив, и посмотреть, как они меняются:
const items = [1, 2];
// Ссылаются на один и тот же массив
const items2 = items;
items2.push(3);
console.log(items2); // => [1, 2, 3]
console.log(items); // => [1, 2, 3]
items2 === items; // true
Сравнение массивов тоже происходит по ссылке. Это может быть очень неожиданно с непривычки. Одинаковые по структуре массивы имеют разные ссылки и не равны друг другу:
[1,2,3] === [1,2,3]; // false
Более того, если передать массив в какую-то функцию, которая его изменяет, то массив тоже изменится. Ведь в функцию передается именно ссылка на массив. Посмотрите на пример:
const f = (coll) => coll.push('wow');
const items = ['one'];
f(items);
console.log(items); // => [ 'one', 'wow' ]
f(items);
console.log(items); // => [ 'one', 'wow', 'wow' ]
Проектируя функции, работающие с массивами, есть два пути: менять исходный массив или формировать внутри новый и возвращать его наружу. Какой лучше? В подавляющем большинстве стоит предпочитать второй. Это безопасно. Функции, возвращающие новые значения, удобнее в работе, а поведение программы становится в целом более предсказуемым, так как отсутствуют неконтролируемые изменения данных.
Изменение массива может повлечь за собой неожиданные эффекты. Представьте себе функцию last()
, которая извлекает последний элемент из массива. Она могла бы быть написана так:
// Метод .pop извлекает последний элемент из массива
// Он изменяет массив, удаляя оттуда этот элемент
const last = (coll) => coll.pop();
Где-то в коде, вы просто хотели посмотреть последний элемент. А в дополнение к этому, функция для извлечения этого элемента, взяла и удалила его оттуда. Это поведение очень неожиданно для подобной функции. Оно противоречит большому количеству принципов построения хорошего кода (например cqs, этот принцип рассматривается в курсе по функциям). Правильная реализация данной функции выглядит так:
// Метод .at() возвращает элемент массива по указанному индексу
// Он не меняет сам массив
// Индекс -1 означает первый элемент с конца
const last = (coll) => coll.at(-1);
В каких же случаях стоит менять сам массив? Есть ровно одна причина, по которой так делают – производительность. Именно поэтому некоторые встроенные методы массивов меняют их, например reverse()
или sort()
:
const items = [3, 8, 1];
// Нет присвоения результата, массив изменяется напрямую
items.sort();
console.log(items); // => [1, 3, 8]
items.reverse();
console.log(items); // => [8, 3, 1]
https://repl.it/@hexlet/js-arrays-references-sort
Обычно в документации каждой функции отдельно подчеркивают, изменяет ли она исходный массив или возвращает результатом новый массив, не модифицируя исходный. Например, метод concat(), в отличие от sort()
, возвращает новый массив, о чем написано в документации.
Несмотря на то, что подход, меняющий массивы напрямую, сложнее в отладке, его используют в некоторых языках для увеличения эффективности работы. Если массив достаточно большой, то полное копирование окажется дорогой операцией. В реальной жизни (веб-разработчика) это почти никогда не является проблемой, но знать об этом полезно.
Вам ответят команда поддержки Хекслета или другие студенты.
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
Зарегистрируйтесь или войдите в свой аккаунт