Как сделать копию объекта js

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

При копировании объектов и массивов в JavaScript, данные копируются только на один уровень вглубь.

Время чтения: меньше 5 мин

Обновлено 20 декабря 2021

Кратко

При копировании объектов или массивов JavaScript копирует данные только на один уровень вглубь. Этот тип копирования называется поверхностным (shallow).

Если необходимо полностью скопировать сложную структуру данных, например, массив с объектами, то нужно делать глубокое (deep) или полное копирование данных. JavaScript не содержит функций для глубокого копирования, лучший вариант сделать глубокую копию — сериализовать структуру в JSON и тут же распарсить.

Как понять

Проблема поверхностного копирования

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

Если изменять элементы этой структуры после копирования, то эти изменения будут также видны в исходной структуре:

Непримитивные типы данных, такие как массивы и объекты, хранятся по ссылке. Так как копирование происходит только на один уровень вглубь, то при копировании массива происходит копирование ссылок на старые объекты в новый массив.

В итоге получается картина, когда разные массивы ссылаются на одни и те же объекты в памяти:

Результат поверхностного копирования массива

Как получить глубокую копию

JavaScript не содержит отдельных функций для глубокого копирования массивов или объектов. Существуют различные способы сделать глубокое копирование.

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

Можно воспользоваться готовой библиотекой. Например, функцию глубокого копирования содержит популярная библиотека утилит lodash. Функция гарантировано работает в подавляющем большинстве случаев, потому что используется в десятках тысяч проектов каждый день. Исходный код библиотеки открыт, можно изучить исходный код функции глубокого копирования.

Самый быстрый способ глубокого копирования звучит глупо — нужно сериализовать копируемый объект в JSON и тут же распарсить его. В результате появится полная копия объекта:

Результат глубокого копирования массива

У этого метода есть ограничение — копируемые данные должны быть сериализуемыми. Если объект содержит методы или массив содержит функции, то копирование с помощью JSON-преобразования не сработает:

У меня есть объект, x . Я хотел бы скопировать его как объект y , так что изменения в y не изменяют x . Я понял, что копирование объектов, полученных из встроенных объектов JavaScript, приведет к дополнительным нежелательным свойствам. Это не проблема, так как я копирую один из моих собственных, построенных буквально объектов.

Как правильно клонировать объект JavaScript?

ОТВЕТЫ

Ответ 1

В дополнение к неперечислимым атрибутам вы столкнетесь с более сложной проблемой при попытке копирования объектов, имеющих скрытые свойства. Например, prototype является скрытым свойством функции. Кроме того, прототип объекта ссылается на атрибут __proto__ , который также скрыт и не будет скопирован с помощью цикла for/in, итерации по атрибутам исходного объекта. Я думаю, что __proto__ может быть специфичным для интерпретатора Firefox JavaScript, и это может быть что-то другое в других браузерах, но вы получаете изображение. Не все перечислимо. Вы можете скопировать скрытый атрибут, если знаете его имя, но я не знаю, как его обнаружить.

Еще одна неприятность в поисках элегантного решения - проблема правильной настройки наследования прототипа. Если прототипом исходного объекта является Object , то просто создание нового общего объекта с <> будет работать, но если исходный прототип является потомком Object , тогда вам не удастся добавить дополнительные элементы из этого прототипа, который вы пропустили с помощью hasOwnProperty , или которые были в прототипе, но не были перечислены в первую очередь. Одним из решений может быть вызов свойства constructor исходного объекта для получения исходного объекта копирования, а затем копирования по атрибутам, но тогда вы все равно не получите неперечислимые атрибуты. Например, объект Date сохраняет свои данные как скрытый элемент:

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

Он не сможет обрабатывать какой-либо объект JavaScript, но его может быть достаточно для многих целей, если вы не предполагаете, что он будет работать только на все, что вы бросаете на него.

Ответ 2

Если вы не используете Date s, функции, undefined или Infinity в вашем объекте, очень простой однострочник - это JSON.parse(JSON.stringify(object)) :

Ответ 3

С помощью jQuery вы можете поверхностную копию с расширением:

последующие изменения в copiedObject не повлияют на originalObject , и наоборот.

Или сделать глубокую копию:

Ответ 4

В ECMAScript 6 существует метод Object.assign, который копирует значения всех перечислимых собственных свойств из одного объекта в другой. Например:

Но имейте в виду, что вложенные объекты все еще копируются как ссылка.

Ответ 5

  • Если вы хотите неглубокую копию, используйте Object.assign(<>, a)
  • Для "глубокой" копии используйте JSON.parse(JSON.stringify(a))

Во внешних библиотеках нет необходимости, но сначала вам необходимо проверить совместимость браузера.

Ответ 6

Таким образом, это не точный ответ на вопрос, но он является однострочным решением и, следовательно, элегантным. И он работает лучше всего для 2-х случаев:

  • Если такое наследование полезно (duh!)
  • Если исходный объект не будет изменен, что делает связь между двумя объектами без проблем.

Почему я считаю это решение превосходным? Он является родным, поэтому нет циклов, нет рекурсии. Однако для старых браузеров потребуется polyfill.

Ответ 7

Метод Object.assign является частью стандарта ECMAScript 2015 (ES6) и делает именно то, что вам нужно.

Метод Object.assign() используется для копирования значений всех перечислимых собственных свойств из одного или нескольких исходных объектов в целевой объект.

polyfill для поддержки старых браузеров:

Ответ 8

Есть несколько проблем с большинством решений в Интернете. Поэтому я решил сделать продолжение, которое включает, почему принятый ответ не должен быть принят.

стартовая ситуация

Я хочу глубоко скопировать Javascript Object со всеми его потомками и их потомками и так далее. Но так как я не нормальный разработчик, у моего Object есть нормальные properties , circular structures и даже nested objects .

Итак, давайте сначала создадим circular structure и nested object .

Давайте свяжем все вместе в Object именем a .

Далее мы хотим скопировать a в переменную с именем b и изменить ее.

Вы знаете, что здесь произошло, потому что если бы не вы, вы бы даже не попали на этот великий вопрос.

Теперь давайте найдем решение.

Первая попытка, которую я попробовал, была с использованием JSON .

Не тратьте на это слишком много времени, вы получите TypeError: Converting circular structure to JSON .

Рекурсивная копия (принятый "ответ")

Давайте посмотрим на принятый ответ.

Хорошо выглядит, а? Это рекурсивная копия объекта и обрабатывает другие типы, такие как Date , но это не было обязательным требованием.

Рекурсия и circular structures не работают вместе. RangeError: Maximum call stack size exceeded

нативное решение

После спора с моим коллегой, мой начальник спросил нас, что случилось, и он нашел простое решение после некоторого поиска в Google. Он называется Object.create .

Это решение было добавлено в Javascript некоторое время назад и даже обрабатывает circular structure .

. и вы видите, это не сработало с вложенной структурой внутри.

полифилл для нативного раствора

В старом браузере есть полифил для Object.create такой же, как IE 8. Это что-то вроде рекомендованного Mozilla, и, конечно, оно не идеально и приводит к той же проблеме, что и собственное решение.

Я поместил F вне области видимости, чтобы мы могли посмотреть, что нам говорит instanceof .

Та же проблема, что и у нативного решения, но немного худший результат.

лучшее (но не идеальное) решение

И давайте посмотрим на вывод.

Требования соответствуют, но все еще есть некоторые меньшие проблемы, в том числе изменение instance nested и circ для Object .

Структура деревьев, которые разделяют лист, не будет скопирована, они станут двумя независимыми листами:

заключение

Последнее решение, использующее рекурсию и кеш, может быть не лучшим, но это настоящая глубокая копия объекта. Он обрабатывает простые properties , circular structures и nested object , но при клонировании испортит их экземпляр.

Ответ 9

Если вы в порядке с неглубокой копией, библиотека underscore.js имеет метод clone.

или вы можете расширить его, как

Ответ 10

Хорошо, представьте, что у вас есть этот объект ниже, и вы хотите его клонировать:

ответ в основном depeneds, на котором ECMAScript вы используете, в ES6+ , вы можете просто использовать Object.assign , чтобы сделать клон:

или используя оператор распространения, как это:

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

Ответ 11

Одним из особенно неэлегантных решений является использование JSON-кодирования для создания глубоких копий объектов, не имеющих методов-членов. Методология заключается в том, что JSON кодирует ваш целевой объект, затем, декодируя его, вы получаете копию, которую ищете. Вы можете декодировать столько раз, сколько хотите сделать столько копий, сколько вам нужно.

Конечно, функции не принадлежат JSON, поэтому это работает только для объектов без методов-членов.

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

Ответ 12

Вы можете просто использовать свойство распространения для копирования объекта без ссылок. Но будьте осторожны (см. Комментарии), "копия" находится на самом низком уровне объекта/массива. Вложенные свойства остаются ссылками!

Полный клон:

Клонирование со ссылками на втором уровне:

JavaScript фактически не поддерживает глубокие клоны изначально. Используйте служебную функцию. Например, Рамда:

Ответ 13

Для тех, кто использует AngularJS, существует также прямой метод клонирования или расширения объектов в этой библиотеке.

Подробнее в angular.copy документация.

Ответ 14

Ответ A.Levy почти завершен, вот мой небольшой вклад: существует способ обработки рекурсивных ссылок, см. эту строку

if(this[attr]==this) copy[attr] = copy;

Если объект является элементом XML DOM, мы должны использовать cloneNode

if(this.cloneNode) return this.cloneNode(true);

Вдохновленный исчерпывающим исследованием A.Levy и прототипом Calvin, я предлагаю это решение:

См. также комментарии Энди Берка в ответах.

Ответ 15

Ответ 16

Вот функция, которую вы можете использовать.

Ответ 17

В ES-6 вы можете просто использовать Object.assign(. ). Пример:

Ответ 18

В ECMAScript 2018

Имейте в виду, что вложенные объекты все еще копируются как ссылка.

Ответ 19

Вы можете клонировать объект и удалять любые ссылки из предыдущего, используя одну строку кода. Просто выполните:

Для браузеров/движков, которые в настоящее время не поддерживают Object.create, вы можете использовать этот polyfill:

Ответ 20

Новый ответ на старый вопрос! Если у вас есть удовольствие от использования ECMAScript 2016 (ES6) с Spread Syntax, это легко.

Это обеспечивает чистый метод для мелкой копии объекта. Создание глубокой копии, означающей makign новой копии каждого значения в каждом рекурсивно вложенном объекте, требует более тяжелых решений выше.

JavaScript продолжает развиваться.

Ответ 21

Интересует клонирование простых объектов:

Ответ 22

Решение ES6, если вы хотите (неглубоко) клонировать экземпляр класса, а не только объект свойства.

Ответ 23

Ответ 24

Я думаю, что есть простой и рабочий ответ. При глубоком копировании есть две проблемы:

  1. Сохраняйте свойства независимыми друг от друга.
  2. И сохранить методы на клонированном объекте.

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

Ответ 25

Для глубокого копирования и клонирования JSON.stringify затем JSON.parse объект:

Ответ 26

В Firefox результат

Ответ 27

Это адаптация кода А. Леви для обработки клонирования функций и множественных/циклических ссылок. Это означает, что если два свойства в клонированном дереве являются ссылками одного и того же объекта, клонированное дерево объектов будут иметь эти свойства, указывающие на один и тот же клон ссылочного объекта. Это также решает случай циклических зависимостей, которые, если их оставить необработанными, приводят к бесконечной петле. Сложность алгоритма O (n)

Некоторые быстрые тесты

Ответ 28

Ответ 29

Так как mindeavor утверждал, что клонируемый объект является объектом с литералом, решение может состоять в том, чтобы просто генерировать объект несколько раз, а не клонирование экземпляра объекта:

Ответ 30

Я написал свою собственную реализацию. Не уверен, что это считается лучшим решением:

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

Копирование объектов в JavaScript может быть непростым делом. Некоторые способы выполняют неглубокое копирование, что в большинстве случаев является поведением по умолчанию.

Глубокая копия против мелкой копии

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

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

При выполненииглубокая копия, тевнешние объекты также копируются, поэтому новый клонированный объект полностью независим от старого.

Самый простой вариант: использовать Lodash

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

Lodash предлагает очень удобный clone и deepclone функции для выполнения поверхностного и глубокого клонирования.

У Lodash есть такая приятная особенность:вы можете импортировать отдельные функции отдельнов вашем проекте, чтобы значительно уменьшить размер зависимости.

Вот пример, показывающий использование этих двух функций:

В этом простом примере мы сначала создаем неглубокую копию и редактируем свойство i.color, которое распространяется на скопированный объект.

В глубоком клоне этого не происходит.

Object.assign ()

Object.assign() выполняет неглубокую копию объекта, а не глубокую копию.

Будучи неглубокой копией, значения клонируются, а ссылки на объекты копируются (а не сами объекты), поэтому, если вы редактируете свойство объекта в исходном объекте, оно также изменяется в скопированном объекте, поскольку внутренний объект, на который имеется ссылка, остается таким же:

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

Воператор распространенияэтоES6 / ES2015функция, которая обеспечивает очень удобный способ выполнения неглубокого клона, эквивалентного тому, что Object.assign() делает.

Неправильные решения

В Интернете вы найдете множество предложений. Вот несколько неправильных:

Использование Object.create ()

Это неправильно, он не копирует.

Вместо этого original объект используется какпрототипиз copied .

Вроде работает, но под капотами нет:

Сериализация JSON

Некоторые рекомендуют трансформироваться вJSON:

но это имеет неожиданные последствия.

Делая это, вы будететерятьлюбое свойство Javascript, не имеющее эквивалентного типа в JSON, например Function или же Infinity . Любое имущество, присвоенное undefined будет проигнорирован JSON.stringify , что приводит к их пропуску на клонированном объекте.

Кроме того, некоторые объекты преобразуются в строки, например объекты Date (также без учета часового пояса и по умолчанию UTC), Set, Map и многие другие:

Parsing as JSON

Это работает, только если у вас нет внутренних объектов и функций, а есть только значения.


Метод assign() - используется для копирования одного или нескольких исходных объектов в целевой объект.

Синтаксис метода assign()

Object.assign(target, src1, src2. )

target - целевой объект;

src1, src2 - исходные объекты.

После копирования метод assign() возвращает целевой объект.

Метод assign() - Примеры

Пример 1.1

Исходный объект add и целевой объект numbers после копирования остаются независимыми объектами .

Это не относится к вложенным объектам . В следующем примере исходный объект add имеет вложенный объект .

Пример 1.2

Итак, исходный объект add имеет вложенный объект.

Что происходит после копирования?

К ак родительские свойства , так и вложенная структура исходного объекта add появляются и у целевого объекта numbers .

Вложенная структура исходного объекта add , как в самом исходном объекте add , так и в целевом объекте numbers имеет ссылочный характер.

1. При изменении родительских свойств исходного объекта add --> аналогичные (скопированные) родительские свойств а целевого объекта numbers НЕ изменяются .

При изменении свойств вложенной структуры исходного объекта add --> изменяются скопированные свойства вложенной структуры целевого объекта numbers .

2. И наоборот : при изменении скопированных свойств вложенной структуры целевого объекта numbers --> изменяются свойства вложенной структуры исходного объекта add .

Таким образом применение метода assign() не дает глубокого копирования объекта : вложенные объекты передаются по ссылке.

Копия только исходного объекта

В рассмотренных выше примерах исходный объект add копировался в целевой объект numbers . Таким образом к свойствам объекта numbers добавлялись свойства объекта add .

Но как сделать копию исходного объекта? В нашем случае, как сделать копию только объекта add ?

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

Пример 2.1

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

Так работает метод assign() в JavaScript - делает поверхностную копию исходного объекта.

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