Понимание разницы между глубоким и поверхностным клонированием, развеивание мифов и изучение доступных вариантов и встроенной поддержки

Глубокое клонирование имеет встроенную поддержку в JavaScript с прошлого года! Но что это значит для вас?

Что вообще такое клонирование?

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

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

Вот что происходит при поверхностном клонировании: есть два объекта (оригинал и его клон, то есть вы и ваша сестра), но один общий вложенный объект или массив, часто называемый « ссылка» (комната, в которой вы оба живете). Если клонированный объект изменит эту ссылку (например, ваши сестры покрасят стены в синий цвет), это изменение также повлияет на оригинал (вы получите синюю стену). Итак, резюмируя: Клон и оригинал используют одни и те же вложенные объекты и массивы, и любые изменения, внесенные во вложенные объекты, повлияют как на клон, так и на оригинал.

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

Почему глубокое клонирование важно?

Теперь, когда мы понимаем, что такое глубокое клонирование, вы можете спросить себя, где бы вы его применяли. В конце концов, его полезность ограничена, если он не имеет практического применения, верно?

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

Распространенные ошибки клонирования в JavaScript

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

const firstSister = {
  name: "Anna",
  wallColor: "white"
}

const secondSister = { ... firstSister}

secondSister.wallColor = "blue";
console.log(firstSister.wallColor); // 'white'
console.log(secondSister.wallColor); //'blue'

Кажется, это работает, верно? По крайней мере, это то, к чему мы стремились — возможность внести изменения в один объект, не затрагивая другой. Но что у нас есть массив вместо простой строки? Что у нас было 2 цвета стен вместо одного?

const firstSister = {
  name: "Anna",
  wallColors: ["white", "pink"]
}

const secondSister = { ... firstSister}

secondSister.name = "Emma";
console.log(firstSister.name); // 'Anna'
console.log(secondSister.name); // 'Emma'

secondSister.wallColors[0] = "blue";
console.log(firstSister.wallColors); // ['blue', pink']
console.log(secondSister.wallColors); // ['blue', pink']

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

А как насчет метода Object.assign()? Что ж, извините, что вестник плохих новостей, но он ведет себя так же, как оператор спреда. (Примечание: между ними есть некоторые различия на более глубоком уровне, но они не относятся к рассматриваемой теме.)

Общие решения для глубокого клонирования (эра поддержки до нативной поддержки)

За прошедшие годы разработчики нашли различные подходы к глубокому клонированию, которые соответствуют их потребностям и предпочтениям в кодировании. Вероятно, наиболее широко используемым способом глубокого клонирования является метод JSON.parse(JSON.stringify()) (или "сериализация-десериализация"). Как это работает?

Этот метод использует тот факт, что объект JavaScript можно легко сериализовать и десериализовать, поэтому он просто берет объект, преобразует его в строку JSON с помощью JSON.stringify(), а затем превращает его обратно в строку. объекта путем реализации JSON.parse(). Что эффективно достигается, так это новый глубоко клонированный объект исходного.

Давайте посмотрим на это в действии:

const firstSister = {
  name: "Anna",
  wallColors: ["white", "pink"]
}

const secondSister = JSON.parse(JSON.stringify(firstSister))

secondSister.name = "Emma";
console.log(firstSister.name); // 'Anna'
console.log(secondSister.name); // 'Emma'

secondSister.wallColors[0] = "blue";
console.log(firstSister.wallColors); // ['white', pink']
console.log(secondSister.wallColors); // ['blue', pink']

Кажется, мы наконец-то достигли своей цели! И если вы думаете, что это можно сделать проще, вы не единственный. Вы всегда можете использовать предопределенные методы из библиотеки, упрощающей задачу, например, метод .deepClone() из Lodash. Но что, если я скажу вам, что есть еще более простой метод?

Нативно поддерживаемый метод глубокого клонирования в JavaScript

Метод structuredClone() был первоначально представлен в Node 17 в апреле 2022 года и, таким образом, реализовал долгожданную встроенную поддержку глубокого клонирования в JavaScript. Проверим, как он ведет себя в действии:

const firstSister = {
  name: "Anna",
  wallColors: ["white", "pink"]
}

const secondSister = structuredClone(firstSister);

secondSister.name = "Emma";
console.log(firstSister.name); // 'Anna'
console.log(secondSister.name); // 'Emma'

secondSister.wallColors[0] = "blue";
console.log(firstSister.wallColors); // ['white', pink']
console.log(secondSister.wallColors); // ['blue', pink']

Довольно аккуратно! Это дает нам действительно простое решение довольно сложной проблемы глубокого клонирования в JavaScript. Однако, как и любой другой метод, этот также не идеален и имеет некоторые ограничения. Один из самых больших недостатков — это когда вы пытаетесь клонировать объект, который содержит функцию. Обычно вы ожидаете, что функция также будет клонирована. Но по факту он полностью отбрасывается.

const firstSister = {
  name: "Anna",
  wallColors: ["white", "pink"],
  paintWalls: function(index, newColor) { this.wallColors[index] = newColor }
}

const secondSister = structuredClone(firstSister); // Failed to execute 'structuredClone' on 'Window': function(index, color) { this.wallColors[index] = color } could not be cloned.

firstSister.paintWalls(0, "blue");
console.log(firstSistem.wallColors); // ['blue', 'pink']

Как видно из предоставленного кода, функция работает так, как задумано в первом объекте. Однако при второй попытке клонировать указанный объект с помощью метода structuredClone() возникает ошибка. Но мы не можем считать это недостатком метода, поскольку метод JSON.parse(JSON.stringify()) также не поддерживает функции клонирования. Причина этого в том, что он может сериализовать только те значения, которые могут быть представлены в виде объектов JSON, а функций среди них просто нет. Единственная заметная разница в том, что этот метод не выдаст ошибку, он просто проигнорирует функции и клонирует все остальное. Для объектов глубокого клонирования, содержащих функции, вам, к сожалению, все равно придется создавать собственные библиотеки или искать некоторые обходные пути.

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

Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .

Заинтересованы в масштабировании запуска вашего программного обеспечения? Ознакомьтесь с разделом Схема.