Как клонировать массив в js
Независимое глубокое клонирование объектов в JavaScript
Как типы данных передаются в функции в JavaScript? Каждый js-программист наверняка без труда ответит на этот вопрос, но все же скажем: примитивные типы данных передаются в функцию всегда только по значению, а ссылочные всегда только по ссылке. И вот тут с последними, в некоторых ситуациях, возникают проблемы. Давайте рассмотрим пример:
В данном случае, мы просто объявили массив чисел и вывели его в консоли. Теперь передадим его в функцию, которая возвращает новый массив, но с добавлением некоторого значения во втором аргументе, к концу нового массива:
Старый массив не изменился, потому что мы получили каждый его элемент и по отдельности присвоили значения элемента к элементам нового массива. Теперь последний имеет отдельную область памяти и если его изменить, то старого массива это никак не коснется. Но все это простые примеры и в реальных программах, скорее всего будут встречаться не только одномерные массивы, а и двумерные, реже трехмерные, еще реже четырехмерные. Преимущественно они встречаются в виде ассоциативных массивов (хеш-таблицы). В JavaScript чаще всего это объекты.
Давайте рассмотрим стандартные способы копирования объектов, которые предоставляет JavaScript — Object.assign() используется для копирования значений всех собственных перечисляемых свойств из одного или более исходных объектов в целевой объект. После копирования он возвращает целевой объект. Рассмотрим его:
И снова старая проблема, мы ссылаемся на одну и ту же область памяти, что приводит к модификации двух объектов сразу — изменяя один будет изменяться и другой. Что же делать, если нам нужно получить копию сложного объекта (с множественным разветвлением) и при этом, изменяя объект, не модифицировать другой? Ответить на этот вопрос и есть цель данной статьи. Дальше мы рассмотрим, как написать собственный метод глубокого клонирования (копирования) объектов любого ветвления. Преступим к написанию кода.
2 шаг: присваиваем объект Z объекту refToZ для того, чтобы показать разницу между обычным присваиванием и глубоким клонированием:
5 шаг: на последнем шаге мы просто для сравнения к объекту refToZ добавим свойство addToZ со значением 100 и выведем все три объекта в консоль:
Немного остановимся над реализацией данной функции. Последняя находит, любую вложенность объекта, даже не зная ее. Как она это делает? Все дело в том, что в данном случае мы применяем известный алгоритм для графов — поиск в глубину. Объект — граф, который имеет одну или множество веток, которые в свою очередь могут иметь свои ветки и тд. Чтобы нам найти все нам нужно зайти в каждую ветку и продвигаться в ее глубь, таким образом мы найдем каждый узел в графе и получим его значения. Поиск в глубину можно реализовать 2 способами: рекурсией и с помощью цикла. Второй может оказаться быстрее, так как не будет заполнять стек вызовов, что в свою очередь делает рекурсия. В нашей реализации функции deepClone мы применили комбинацию рекурсию с циклом. Если хотите почитать книги об алгоритмах, то советую начать Адитъя Бхаргава «Грокаем алгоритмы» или более углубленное Томас Кормен «Алгоритмы: построение и анализ».
Подведем итоги, мы вспомнили об типах данных в JavaScript и как они передаются в функции. Рассмотрели простой пример независимого клонирования простого одномерного массива. Рассмотрели одну из стандартных реализации языка для копирования объектов и в итоге написали маленький (по размеру) функцию для независимого глубокого клонирования сложных объектов. Подобная функция может найти свое применения как на стороне сервера (Node js), что более вероятно, так и на клиенте. Надеюсь данная статья была полезна для вас. До новых встреч.
Копировать массив или объект JS по правилам
Подробный разбор правильного копирования массивов и объектов с примерами кода на JavaScript.
Как только вы решили копировать массив или объект в JavaScript следует вспомнить о том, что массивы и объекты являются изменяемыми(mutable), в отличие от примитивных переменных и хранятся как ссылки.
Изменяемые, это те, состояние которых может быть изменено после их создания.
На практике это будет означать следующее:
Поэтому необходимо создавать копию данных, а не делать ссылку на существующие.
Копировать массив или объект в JS не так просто как кажется
Для этого необходимо не просто использовать знак равенства для присвоения новой переменной старых значений, а производить клонирование, иначе вы просто создадите ссылку и будете работать с исходным объектом.
Использовать функцию slice() для копирования массива
Эта функция используется для копирования части массива. Если не указывать параметры, то массив будет копироваться целиком.
Другой способ с использованием Array.from :
Если бы sheeps был бы объектом, то копировать следовало бы так:
Использовать Spread syntax чтобы копировать массив или объект
Spread syntax, появившийся в ES6, позволяет “вытаскивать” перебираемые элементы из своего контейнера.
Неглубокое копирование
Обратите внимание на то, что происходит клонирование только одного уровня – поверхностное копирование!
Если у вас многомерный массив для создания копии каждого уровня придется применить один из предыдущих способов к каждому уровню отдельно.
Если у вас смешанные данные, например, когда массив содержит объекты, в которых значениями могут быть тоже объекты или массивы, то следует использовать рекурсивную функцию, в которой вы будете проверять тип данных и в зависимости от этого копировать массив, объект или примитивную переменную.
Лучший способ клонировать многомерный массив или объект
На данный момент наиболее простым способом клонирования массивов и объектов является преобразование данных в строку, а за тем обратное преобразование в объект с помощью JSON :
Подводя итог
Статья подготовлена по материалам следующих источников:
3 способа клонирования объектов в JavaScript
Nov 19, 2019 · 4 min read
Объекты — это ссылочные типы
Оба объекта выдают одно и то же. На данный момент никаких проблем. Рассмотрим, что произойдет после редактирования второго объекта:
Использование Spread
С помощью s p read можно клонировать объект. Обратите внимание, что копия будет неглубокой. На момент публикации этого руководства оператор spread для клонирования объектов находился на стадии 4, соответственно официально он не указан в спецификациях. Поэтому для того, чтобы его использовать, нужно выполнить компиляцию с Babel (или чем-то подобным).
Использование Object.assign
Использование JSON
Этот способ предоставляет глубокую копию. Стоит упомянуть, что это быстрый и грязный способ глубокого клонирования объекта. В качестве более надежного решения рекомендуется использовать что-то вроде lodash.
Lodash DeepClone или JSON?
Глубокое или неглубокое клонирование?
При использовании spread для копирования объекта создается неглубокая копия. Если массив является вложенным или многомерным, этот способ не будет работать. Рассмотрим пример:
Таким образом, клонированный объект был изменен с добавлением city. В результате получаем:
Неглубокая копия предполагает копирование первого уровня и ссылается на более глубокие уровни.
Глубокая копия
Возьмем тот же пример, но применим глубокую копию с использованием JSON:
Глубокая копия является копией для вложенных объектов. Однако иногда достаточно использования неглубокой копии.
Производительность
Object.assign и Spread
Стоит отметить, что Object.assign — это функция, которая модифицирует и возвращает целевой объект. В данном примере при использовании:
С другой стороны, Spread — это оператор, который копирует свойства одного объекта в новый объект. При репликации приведенного выше примера с помощью spread для изменения переменной food.
3 способа клонирования объектов в JavaScript
Объекты — это ссылочные типы
Оба объекта выдают одно и то же. На данный момент никаких проблем. Рассмотрим, что произойдет после редактирования второго объекта:
Использование Spread
С помощью spread можно клонировать объект. Обратите внимание, что копия будет неглубокой. На момент публикации этого руководства оператор spread для клонирования объектов находился на стадии 4, соответственно официально он не указан в спецификациях. Поэтому для того, чтобы его использовать, нужно выполнить компиляцию с Babel (или чем-то подобным).
Использование Object.assign
Использование JSON
Этот способ предоставляет глубокую копию. Стоит упомянуть, что это быстрый и грязный способ глубокого клонирования объекта. В качестве более надежного решения рекомендуется использовать что-то вроде lodash.
Lodash DeepClone или JSON?
Глубокое или неглубокое клонирование?
При использовании spread для копирования объекта создается неглубокая копия. Если массив является вложенным или многомерным, этот способ не будет работать. Рассмотрим пример:
Таким образом, клонированный объект был изменен с добавлением city. В результате получаем:
Неглубокая копия предполагает копирование первого уровня и ссылается на более глубокие уровни.
Глубокая копия
Возьмем тот же пример, но применим глубокую копию с использованием JSON:
Глубокая копия является копией для вложенных объектов. Однако иногда достаточно использования неглубокой копии.
Производительность
Object.assign и Spread
Стоит отметить, что Object.assign — это функция, которая модифицирует и возвращает целевой объект. В данном примере при использовании:
С другой стороны, Spread — это оператор, который копирует свойства одного объекта в новый объект. При репликации приведенного выше примера с помощью spread для изменения переменной food.
Копирование объектов и ссылки
Одним из фундаментальных отличий объектов от примитивных типов данных является то, что они хранятся и копируются «по ссылке».
Примитивные типы: строки, числа, логические значения – присваиваются и копируются «по значению».
Объекты ведут себя иначе.
Переменная хранит не сам объект, а его «адрес в памяти», другими словами «ссылку» на него.
Сам объект хранится где-то в памяти. А в переменной user лежит «ссылка» на эту область памяти.
Когда переменная объекта копируется – копируется ссылка, сам же объект не дублируется.
Если мы представляем объект как ящик, то переменная – это ключ к нему. Копирование переменной дублирует ключ, но не сам ящик.
Теперь у нас есть две переменные, каждая из которых содержит ссылку на один и тот же объект:
Мы можем использовать любую из переменных для доступа к ящику и изменения его содержимого:
Приведённый выше пример демонстрирует, что объект только один. Как если бы у нас был один ящик с двумя ключами и мы использовали один из них ( admin ), чтобы войти в него и что-то изменить, а затем, открыв ящик другим ключом ( user ), мы бы увидели эти изменения.
Сравнение по ссылке
Операторы равенства == и строгого равенства === для объектов работают одинаково.
Два объекта равны только в том случае, если это один и тот же объект.
В примере ниже две переменные ссылаются на один и тот же объект, поэтому они равны друг другу:
В другом примере два разных объекта не равны, хотя оба пусты:
Для сравнений типа obj1 > obj2 или для сравнения с примитивом obj == 5 объекты преобразуются в примитивы. Мы скоро изучим, как работают такие преобразования объектов, но, по правде говоря, сравнения такого рода необходимы очень редко и обычно являются результатом ошибки программиста.
Клонирование и объединение объектов, Object.assign
Таким образом, при копировании переменной с объектом создаётся ещё одна ссылка на тот же самый объект.
Но что, если нам всё же нужно дублировать объект? Создать независимую копию, клон?
Это выполнимо, но немного сложно, так как в JavaScript нет встроенного метода для этого. На самом деле, такая нужда возникает редко. В большинстве случаев нам достаточно копирования по ссылке.
Но если мы действительно этого хотим, то нам нужно создавать новый объект и повторять структуру дублируемого объекта, перебирая его свойства и копируя их.
Кроме того, для этих целей мы можем использовать метод Object.assign.
Например, объединим несколько объектов в один:
Если принимающий объект ( user ) уже имеет свойство с таким именем, оно будет перезаписано:
Мы также можем использовать Object.assign для замены for..in на простое клонирование:
Этот метод скопирует все свойства объекта user в пустой объект и возвратит его.
Вложенное клонирование
До сих пор мы предполагали, что все свойства объекта user хранят примитивные значения. Но свойства могут быть ссылками на другие объекты. Что с ними делать?
Например, есть объект:
Чтобы исправить это, мы должны в цикле клонирования делать проверку, не является ли значение userКак клонировать массив в js объектом, и если это так – скопировать и его структуру тоже. Это называется «глубокое клонирование».
Мы можем реализовать глубокое клонирование, используя рекурсию. Или, чтобы не изобретать велосипед, использовать готовую реализацию — метод _.cloneDeep(obj) из JavaScript-библиотеки lodash.
Итого
Объекты присваиваются и копируются по ссылке. Другими словами, переменная хранит не «значение объекта», а «ссылку» (адрес в памяти) на это значение. Поэтому копирование такой переменной или передача её в качестве аргумента функции приводит к копированию этой ссылки, а не самого объекта.
Все операции с использованием скопированных ссылок (например, добавление или удаление свойств) выполняются с одним и тем же объектом.