Найти в Дзене

Копирование объектов в JS

Перевод этой статьи. В этой статье мы рассмотрим разные способы поверхностного и глубокого копирования объектов в JavaScript. Для начала стоит освежить пару базовых принципов. Объекты в JavaScript – это просто ссылки на участок в памяти. Эти ссылки мутабельны, т.е. их можно переназначить. Когда мы делаем копию такой ссылки, мы просто получаем 2 ссылки, которые указывают на один и тот же участок в памяти: В примере выше обе переменные – foo и bar – будут меняться при изменении одной из них. Это яркий пример того, как копирование объектов в JavaScript требует внимательности в зависимости от вашего кейса. Поверхностное копирование Если объект состоит только из примитивных данных, то для копирования можно использовать Object.assign(...). Обратите внимание, что оба метода (Object.assign и spread operator) позволяют копировать свойства из нескольких объектов в один: Проблема этих методов в том, что для тех свойств объекта, которые сами являются объектами, копируются только ссылки. Иными слов

Перевод этой статьи.

В этой статье мы рассмотрим разные способы поверхностного и глубокого копирования объектов в JavaScript.

Для начала стоит освежить пару базовых принципов. Объекты в JavaScript – это просто ссылки на участок в памяти. Эти ссылки мутабельны, т.е. их можно переназначить. Когда мы делаем копию такой ссылки, мы просто получаем 2 ссылки, которые указывают на один и тот же участок в памяти:

В примере выше обе переменные – foo и bar – будут меняться при изменении одной из них. Это яркий пример того, как копирование объектов в JavaScript требует внимательности в зависимости от вашего кейса.

Поверхностное копирование

Если объект состоит только из примитивных данных, то для копирования можно использовать Object.assign(...).

-2

Обратите внимание, что оба метода (Object.assign и spread operator) позволяют копировать свойства из нескольких объектов в один:

-3

Проблема этих методов в том, что для тех свойств объекта, которые сами являются объектами, копируются только ссылки. Иными словами, это то же самое, что копирование через присвоение var bar = foo, как в первом примере:

-4

Глубокое копирование (с оговоркой)

Для глубокого копирования объекта может подойти сериализация объекта в строку, а затем десериализация обратно:

-5

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

Например, несмотря на то, что объект Date нормально выводится в ISO формате при сериализации, если попробовать воспользоваться JSON.parse, дата не превратится обратно в объект Date, а останется строкой.

Глубокое копирование (с меньшими оговорками)

Для более сложных случаев можно воспользоваться алгоритмом структурного клонирования из HTML5. К сожалению, на момент написания статьи не все типы поддерживаются, но все равно поддерживается больше типов, чем в JSON.parse: Date, RegExp, Map, Set, Blob, FileList, ImageData, разреженный массив (пропущены индексы) и типизированный массив. Также внутри клонированных данных сохраняются ссылки, что позволяет использовать циклические и рекурсивные структуры, которые не работают при использовании метода сериализации, который упомянут выше.

На данный момент нет возможности напрямую использовать алгоритм структурного копирования, но в современных браузерах есть функции, которые используют этот алгоритм «под капотом». Таким образом, есть несколько хаков, которые теоретически можно использовать для глубокого копирования объектов.

Через MessageChannel: идея в том, чтобы использовать алгоритм сериализации из MessageChannel. Эта операция является асинхронной:

-6

Через history API:

history.pushState() и history.replaceState() – оба создают структурированный клон первого аргумента! Однако из-за того, что методы синхронны, они не очень быстрые и их частый вызов может привести к тому, что браузер перестанет реагировать на действия пользователя

-7

Через notifications API: при создании уведомления конструктор создаёт структурную копию передаваемых данных. После этого браузер попытается вывести уведомление. До тех пор, пока пользователь не даст на это разрешение во всплывающем окне, уведомление будет тихо падать. Если пользователь дал разрешение, то уведомление будет закрываться сразу после вызова.

-8

Глубокое копирование в node.js

В Node.js версии 8.0.0 появился API для сериализации, который совместим со структурным копированием. На момент написания статьи этот API является экспериментальным.

-9

Те, кто использует версии ниже 8.0.0, либо те, кто хочет использовать более стабильную реализацию, могут использовать cloneDeep из lodash, который основан на структурном алгоритме клонирования.

Заключение

Выбор лучшего алгоритма для копирования объектов в JavaScript зависит от контекста и типа объектов, которые вы собираетесь копировать. В общем случае, самым безопасным способом копирования будет lodash, однако вы можете самостоятельно реализовать более эффективную имплементацию. Например, вот пример глубокого копирования, который работает в том числе и для дат:

-10

Лично я жду, когда можно будет везде использовать структурное клонирование и покончить наконец с этой проблемой. Счастливого клонирования :)