Как сделать копию массива js

Добавил пользователь Алексей Ф.
Обновлено: 05.10.2024

Массив — это структура, в которой можно хранить коллекции элементов — чисел, строк, других массивов и т.д. Элементы нумеруются и хранятся в том порядке, в котором их поместили в массив. Элементов может быть сколько угодно, они могут быть какими угодно.

Массивы очень похожи на нумерованные списки.

Как пишется

Создадим массив с помощью квадратных скобок [] .

К примеру, можно создать пустой массив:

А можно создать сразу с элементами внутри:

Элементы могут быть разных типов:

Как это понять

Количество доступных ячеек называют размером или длиной массива. В JavaScript длина массива обычно совпадает с количеством элементов в нем. Массивы хранят свой размер в свойстве length :

💡 Чтение

Обратись к конкретному индексу, чтобы получить содержимое ячейки с этим номером. Если ячейка пустая или такой ячейки нет, то JavaScript вернёт undefined :

💡 Запись

Используй комбинацию чтения и оператора присваивания:

💡 Добавление элементов

Частая операция. Используй методы:

  • push — для добавления в конец массива
  • unshift — для добавления в начало массива

Лучше использовать push , он работает быстрее. Оба принимают произвольное количество аргументов. Все аргументы будут добавлены в массив. Методы возвращают размер массива после вставки:

💡 Создать большой массив из чисел

С помощью цикла и метода push можно быстро создать большой массив с числами.

Создадим массив чисел от 1 до 1000:

Создадим массив чётных чисел от 0 до 1000:

💡 Поиск по массиву

Используй indexOf , чтобы найти, под каким индексом хранится элемент.

Используйте includes , чтобы проверить, что элемент есть в массиве:

На практике

Николай Лопин

🛠 Копирование массива

С копированием есть хитрость. Массив — большая структура, и она не вмещается в одну переменную. Переменная хранит адрес, где находится массив. Если этого не знать, то результат такого кода будет выглядеть странно:

Хитрость в том, что во второй строке происходит копирование адреса, где находится массив, а не самого массива. В итоге получаем ситуацию, когда две переменные iWatched и vitalikWatched работают с одним массивом, так как хранят один адрес. Это особенность работы со значениями, которые хранятся по ссылке.

Копия массива создаётся с помощью метода slice . Нужно вызвать его без аргументов и сохранить результат в новую переменную:

🛠 Деструктуризация массива

В современном JavaScript очень популярна деструктуризация массивов. Этот подход позволяет создавать переменные из элементов массива в одну строку:

В старом подходе, если из массива нужна пара значений, то их читают и сохраняют в переменные:

JavaScript

Объекты — это ссылочные типы

Почему нельзя использовать = ? Посмотрим, что может произойти:

Оба объекта выдают одно и то же. На данный момент никаких проблем. Рассмотрим, что произойдет после редактирования второго объекта:

obj2 был изменен, однако изменения коснулись и obj . Причина заключается в том, что объекты являются ссылочными типами. Поэтому при использовании = , указатель копируется в область занимаемой памяти. Ссылочные типы не содержат значений, они являются указателем на значение в памяти.

Использование Spread

С помощью spread можно клонировать объект. Обратите внимание, что копия будет неглубокой. На момент публикации этого руководства оператор spread для клонирования объектов находился на стадии 4, соответственно официально он не указан в спецификациях. Поэтому для того, чтобы его использовать, нужно выполнить компиляцию с Babel (или чем-то подобным).

Использование Object.assign

Object.assign , выпущенный официально, также создает неглубокую копию объекта.

Использование JSON

Этот способ предоставляет глубокую копию. Стоит упомянуть, что это быстрый и грязный способ глубокого клонирования объекта. В качестве более надежного решения рекомендуется использовать что-то вроде lodash.

Lodash DeepClone или JSON?

  • JSON.stringify/parse работает только с литералом Number, String и Object без функции или свойства Symbol.
  • deepClone работает со всеми типами, а функция и символ копируются по ссылке.

Глубокое или неглубокое клонирование?

При использовании spread для копирования объекта создается неглубокая копия. Если массив является вложенным или многомерным, этот способ не будет работать. Рассмотрим пример:

Таким образом, клонированный объект был изменен с добавлением city. В результате получаем:

Неглубокая копия предполагает копирование первого уровня и ссылается на более глубокие уровни.

Глубокая копия

Возьмем тот же пример, но применим глубокую копию с использованием JSON:

Глубокая копия является копией для вложенных объектов. Однако иногда достаточно использования неглубокой копии.

Производительность

К сожалению, на данный момент нельзя написать тестирование для spread, поскольку официально он не указан в спецификации. Однако результат показывает, что Object.assign намного быстрее, чем JSON . Тест на производительность можно найти здесь.

Object.assign и Spread

Стоит отметить, что Object.assign — это функция, которая модифицирует и возвращает целевой объект. В данном примере при использовании:

<> — это модифицируемый объект. В этой точке на целевой объект не ссылаются никакие переменные, но поскольку Object.assign возвращает целевой объект, то можно сохранить полученный присвоенный объект в переменную cloneFood . Данный пример можно изменить следующим образом:

Очевидно, что значение beef в объекте food неверно, поэтому нужно назначить правильное значение beef с помощью Object.assign . На самом деле мы не используем возвращаемое значение функции, а изменяем целевой объект, на который ссылаемся с помощью константы food .

С другой стороны, Spread — это оператор, который копирует свойства одного объекта в новый объект. При репликации приведенного выше примера с помощью spread для изменения переменной food.

. мы получаем ошибку в результате использования spread для создания новых объектов и, следовательно, присваиваем новый объект для food , который был объявлен с помощью const , что недопустимо. Поэтому можно либо объявить новую переменную для хранения нового объекта, как показано ниже:

Spread оператор (оператор расширения) 'берет' каждый отдельный элемент итерируемого объекта (массив) и 'распаковывает' его в другой итерируемый объект (массив).

Что такое итерируемые (перебираемые) объекты?

К итерируемым объектам можно отнести все, что можно перебрать с помощью цикла for..of . Большая часть задач, где приходится использовать оператор spread, касается массивов и строк.

Где и когда используется spread?

Предположим у нас есть 2 массива с сериалами.

В какой-то момент мы решаем создать еще один массив, который будет включать все сериалы из обоих массивов. Как это сделать?

Мы можем использовать метод .concat() , который возвращает новый массив, состоящий из элементов 1-го и 2-го массива.

Что если мы захотим вставить сериал Sherlock в середину нашего общего списка?

Теоретически, мы также сможем это сделать с помощью метода .concat() :

Если использовать оператор spread, то наш код будет выглядеть более коротко и понятно.

Так будет выглядеть создание одного общего массива с помощью оператора spread:

То есть мы использовали оператор spread, чтобы взять каждый сериал из 1-го и 2-го массива и добавили его в новый массив tvSeries .

Также можно добавить Sherlock в середину общего списка:

Теперь давайте попробуем создать копию нашего массива tvSeries :

Что если мы решим изменить первый элемент нового массива allSeriesList с 'Ozark' на 'Friends'?

Нам удалось поменять 1-й элемент allSeriesList на 'Friends'. Проблема в том, что мы также изменили наш исходный массив tvSeries .

На самом деле мы не создали копию массива. Переменная allSeriesList просто содержит ссылку на наш исходный массив (также как и tvSeries).

1-й способ создать копию массива – использовать метод .concat() :

2-й способ - с помощью оператора spread. Можно создать новый пустой массив и добавить в него каждый элемент исходного массива.

Таким образом, мы использовали оператор spread, чтобы создать копию массива tvSeries . Наши изменения в новом массиве allSeriesList никак не затронули исходный массив tvSeries .

Оператор Rest

Визуально, оператор rest … (три точки) похож на оператор spread, но выполняет противоположную функцию.

Spread забирает каждый элемент из массива и распаковывает в новый массив. Оператор rest забирает каждый элемент из массива и создает из них новый массив.

Есть 2 типа задач, где оператор rest используется чаще всего – в функциях и в процессе деструктуризации.

Пример 1. Оператор rest в функциях

Предположим, мы продаем пиццу. Стоимость нашей самой популярной пиццы “Маргарита” – 500 Руб.

У нас есть функция, которая принимает стоимость конкретной пиццы и количество заказанной пиццы от разных посетителей:

Пример: Стоимость = 500, посетитель A заказал 3 штуки, посетитель B – 10 штук, посетитель C – 6 штук, D – 20 штук.

Мы будем записывать стоимость пиццы в переменную price , а количество пицц в переменную rest (можно назвать как угодно).

Таким образом, мы можем подсчитать стоимость заказа для каждого посетителя:

Мы можем добавить еще одну переменную, которая, например, будет подсчитывать стоимость доставки:

Пример 2. Оператор rest и деструктуризация

Предположим, у нас есть массив, в котором содержится название пиццы, ее 'ID' в нашей системе заказов и количества заказанной пиццы для разных посетителей:

Мы можем получить название пиццы, ее ID и количества и присвоить 3-м новым переменным, используя оператор rest в процессе деструктуризации.

Таким образом мы собрали все количества заказанной пиццы и создали из них отдельный новый массив.

Как скопировать массив на JavaScript

При программировании на JavaScript иногда требуется создать копию массива. Но особенность заключается в том, что в JavaScript массив является объектом, поэтому при использовании оператора присваивания, новой копии создаваться не будет, а просто будет создана ссылка на существующий массив. Если после этого попробовать внести какие-нибудь изменения в любой из этих массивов, то изменения будут внесены сразу в оба. Так получается, потому что фактически это один и тот же объект. Например:

  1. // Как хочется сделать, но не получится
  2. var old_array = [ 'one' , 'two' , 'three' ];
  3. var new_array = old_array ;
  4. // Добавляем по одному элементу в исходный и "новый" массив
  5. old_array . push ( 'banana' );
  6. new_array . push ( 'lemon' );
  7. // Получается вот что
  8. // old_array = ['one', 'two', 'three', 'banana', 'lemon'];
  9. // new_array = ['one', 'two', 'three', 'banana', 'lemon'];

Как видите, несмотря на то, что мы пытались добавить элементы в разные массивы (как нам казалось), они добавились в оба. Чтобы создавать не ссылку, а копию, можно воспользоваться хитрым трюком с методом slice. В этом случае будет создана именно копия массива.

  1. // Корректный вариант решения
  2. var old_array = [ 'one' , 'two' , 'three' ];
  3. var new_array = old_array . slice ( 0 );
  4. // Добавляем по одному элементу в исходный и новый массив
  5. old_array . push ( 'banana' );
  6. new_array . push ( 'lemon' );
  7. // Теперь все правильно
  8. // old_array = ['one', 'two', 'three', 'banana'];
  9. // new_array = ['one', 'two', 'three', 'lemon'];

Надеюсь, что этот совет вам где-нибудь пригодится и поможет избежать хитрых ошибок, которые достаточно сложно отловить.

Комментарии

Даже по указанной в статье ссылке написано, что первый аргумент — required, т.е. необходим. Так что .slice(0), иначе ошибка. В FF так точно.

точнее, в прототипах хранить только неизменяемые методы/свойства, к которым могут относиться, к примеру, и константы.

Хитрые ошибки будут вот тут:
function Collector() >
Collector.prototype = data: [],
add: function(v) this.data.push(v);
>
>;
var collector1 = new Collector();
collector1.add(1);
var collector2 = new Collector();
collector2.add(2);
alert(collector1.data); // [1, 2]

Правильно при этом вот так:
function Collector() this.data = [];
>
Collector.prototype = add: function(v) this.data.push(v);
>
>;
То есть чтобы у каждого экземпляра был свой массив.

Тут, правда, не так чтобы особенно хитро – все логично, но попасться можно.
Особенно с учетом вот такого:
function Counter() >
Counter.prototype = index: 0,
inc: function() ++this.index;
>
>;
var counter1 = new Counter();
counter1.inc();
var counter2 = new Counter();
counter2.inc();
alert(counter1.index + "\n" + counter2.index);

Фокус, соответственно, в том, что ++ делает this.index = this.index + 1, то есть переопределяет свойство, а this уже указывает на экземпляр. И получается примерно такое:
function Counter() this.index = 1;
>
Counter.prototype = index: 0
>;
var counter = new Counter();
alert(Counter.prototype.index + "\n" + counter.index);


(Я правильно понимаю, что в коменнтариях блоки с кодом не поддерживаются?)

Читайте также: