![Какой метод глубокого клонирования в JavaScript наиболее эффективный - исследование Какой метод глубокого клонирования в JavaScript наиболее эффективный - исследование](https://nuancesprog.ru/wp-content/uploads/2023/06/JS-pattern-696x392.png)
В данном исследовании сравниваются различные методы глубокого клонирования в JavaScript, а их эффективность оценивается с помощью серии модульных тестов. Результаты были проанализированы и ранжированы, чтобы определить наиболее эффективный метод.
Код, созданный в этом исследовании, находится в открытом доступе здесь. Взглянуть на список протестированных методов клонирования можно здесь.
Настоятельно рекомендуется просмотреть код: там есть много того, о чем не идет речь в этой статье.
Выбранные для исследования методы были получены из ответов на StackOverflow, библиотек, таких как Lodash и clone-deep, или являются нативными методами (structuredClone
).
В исследовании не было уделено внимание неглубокому клонированию и вопросам производительности.
Какой метод клонирования считается эффективным?
Эффективная функция клонирования удовлетворяет двум условиям:
- возвращает точную копию входных данных;
- копия должна быть независима от оригинала.
Методология
Для оценки методов клонирования был проверен каждый из них с помощью нескольких тестов.
Список тестов разделен на 4 файла:
В этой статье не будут приведены результаты клонирования массивов: поскольку массивы являются объектами, результаты их клонирования незначительно отличаются от результатов клонирования объектов.
Общие результаты были плохими… шокирующе плохими.
Клонирование примитивных типов
# Пример того, как можно запустить тестовый файл примитивных типов
npm run test -- test/deep-clone/primitive-types.spec.js
В первом тесте, тестовом наборе primitive-types.spec.js, была предпринята попытка клонировать семь примитивных типов JavaScript: string
, number
, boolean
, undefined
, null
, Symbol
и BigInt
.
![](https://nuancesprog.ru/wp-content/uploads/2023/06/1-1.png)
Обзор результатов тестирования
![](https://nuancesprog.ru/wp-content/uploads/2023/06/2-1.png)
❌ = выбранный метод не смог клонировать данный тип.
✅ = выбранный метод успешно клонировал данный тип.
Худший метод
Наименее эффективным методом клонирования оказался простой cloneJSON
.
Этот метод состоит из простого JSON.parse после JSON.stringify. К сожалению, он широко используется.
# реализация cloneJSON
function cloneJSON(obj) {
return JSON.parse(JSON.stringify(obj))
}
cloneJSON
— очень медленный метод с ограниченными возможностями. Поэтому стоит избегать его.
Неожиданность
structuredClone
не может клонировать данные типа Symbol
. Он выбрасывает DOMException с кодом 25, DataCloneError
.
Выбрасывание ошибки кажется несколько излишним. Почему бы просто не скопировать ссылку, ведь данные типа Symbol
уникальны и неизменяемы?
Этот метод появился сравнительно недавно: впервые он был реализован в Node 17. Проблемы с ним на этом не заканчиваются.
![](https://nuancesprog.ru/wp-content/uploads/2023/06/3-8.png)
Клонирование объектов
# Пример того, как можно запустить тестовый файл типа объекта
npm run test -- test/deep-clone/objects.spec.js
Во втором тесте была предпринята попытка клонировать объекты JavaScript, используя набор тестов objects.spec.js.
Здесь можно посмотреть, как был реализован каждый тест.
Ниже представлен обзор выполненных тестов.
![](https://nuancesprog.ru/wp-content/uploads/2023/06/4-2.png)
Сравним результаты каждого метода клонирования для каждого из четырех заголовков:
- basic (базовый);
- property descriptors (дескрипторы свойств);
- object state (состояние объекта);
- prototype (прототип).
Результаты тестирования: базовый
![](https://nuancesprog.ru/wp-content/uploads/2023/06/5-5.png)
Результаты оказались нормальными, за исключением теста на циркулярную ссылку (circular reference test), где большинство методов потерпели неудачу. Здесь можно увидеть, как реализован этот тест.
![](https://nuancesprog.ru/wp-content/uploads/2023/06/6-4.png)
Данный тест не идеален, но позволяет многое проверить:
- циркулярную ссылку от вложенного свойства к корневому объекту;
- циркулярную ссылку от вложенного свойства к другому вложенному свойству;
- установление того факта, что разрешение циклической зависимости не приводит к дублированию объектов;
- клон и вход не являются одним и тем же объектом;
- клон глубоко равен входу.
Вот ряд случайных примеров сложных ситуаций, которые этот тест не охватил:
- циркулярная ссылка от прототипа к корневому объекту;
- циркулярная ссылка от неперечислимого свойства к вложенному объекту;
- вложенные свойства, которые ссылаются на неперечислимые свойства в прототипе объекта, и др.
Результаты тестирования: дескрипторы свойств
![](https://nuancesprog.ru/wp-content/uploads/2023/06/7-3.png)
Все методы игнорировали геттеры, сеттеры и свойства объекта, которые можно прописывать или конфигурировать.
Лучше всего показал себя cloneLib
(npm install clone)! Этот метод может клонировать неперечислимые свойства и данные типа Symbol
как собственные свойства. Ни один другой метод не копирует неперечислимые свойства, что, на мой взгляд, является довольно странным.
![](https://nuancesprog.ru/wp-content/uploads/2023/06/8-2.png)
Результаты тестирования: состояние объекта
![](https://nuancesprog.ru/wp-content/uploads/2023/06/9-2.png)
Ни один из методов не копирует состояние Object.freeze()
, Object.seal()
и Object.preventExtensions()
.
![](https://nuancesprog.ru/wp-content/uploads/2023/06/10-1.png)
Результаты тестирования: прототип
![](https://nuancesprog.ru/wp-content/uploads/2023/06/11.png)
Только три библиотеки могут правильно клонировать прототип.
Были использованы два метода:
structuredClone
копировал прототип в новый независимый объект;- клоны
cloneDeep
иcloneLib
ссылаются на тот же объект, что и во входных данных.
Оба подхода можно считать корректными, но предпочтительней, на наш взгляд, structuredClone
.
Хотя это ужасная практика, некоторые библиотеки меняют прототип во время выполнения, поэтому предпочтительнее ссылаться на входной прототип вместо реального клонирования.
Примечательно, что cloneDeep
не может скопировать неперечислимые свойства в обычном объекте, но может сделать это внутри прототипа.
![](https://nuancesprog.ru/wp-content/uploads/2023/06/0-oOwbYEj5_z_Glfle.png)
Третий тест снова показывает, что structuredClone
не очень хорошо работает с неперечислимыми свойствами.
Клонирование функций
# Пример того, как можно запустить тестовый файл типа функций
npm run test -- test/deep-clone/functions.spec.js
В JavaScript нет подходящего способа клонировать функцию, ни один из методов не сработал.
Результаты можно разделить на три группы.
1. Методы, которые ничего не делают, даже не создают вызываемую функцию:
cloneJSON
;structuredClone
;cloneTrincot
;cloneDeepLodash
.
2. Методы, которые просто ссылаются на исходную функцию:
underscoreContribClone
;cloneNemisj
;cloneKoolDandy
;cloneRfdc
;justClone
;cloneDeep
;cloneLib
.
3. Методы, которые, как кажется, создают независимую и вызываемую функцию, но это не так:
cloneFunctionsUsingBind
.
![](https://nuancesprog.ru/wp-content/uploads/2023/06/13.png)
Этот метод производит связанную функцию, которая является просто оберткой исходной функции.
Данный метод основан на этом ответе на StackOverflow. Ниже можете увидеть “foo” и его клон, созданный этим методом:
![](https://nuancesprog.ru/wp-content/uploads/2023/06/0-Mi-eG2USww4XfGUN-1.png)
![](https://nuancesprog.ru/wp-content/uploads/2023/06/0-F6oK5o2pYbJXZ58r.png)
Трудно сказать, какой метод стоит использовать для клонирования функции.
Если бы клонирование функций в JavaScript было совершенно ненужным, в сети не поднималась бы эта тема.
Результаты клонирования функций были проигнорированы для получения итогового результата, приведенного ниже.
Подсчет результатов
![](https://nuancesprog.ru/wp-content/uploads/2023/06/0-deYdxhewpnbJCHdH-1.png)
Победителем стал cloneLib
(npm install clone).
Итоги
- В JavaScript нет ни одной полностью работоспособной библиотеки и метода глубокого клонирования.
cloneLib
(npm install clone) — лучший метод.cloneJSON
не следует использовать.structuredClone
, хотя и является нативным для Node 17, несовместим со многими особенностями языка.- В других языках, таких как Java, каждый класс должен реализовывать свой метод клонирования, а в JavaScript пытаются написать “универсальный” метод клонирования. Вероятно, подход Java заслуживает большего внимания: вместо
structuredClone
предпочтительней иметь.clone
в прототипе объекта, который можно переопределить в классе/объекте, если метод по умолчанию недостаточно хорош. - Универсальные решения плохи по определению. Они соблазняют своим удобством, но в итоге всегда терпят неудачу перед лицом возрастающей сложности.
- Нельзя утверждать, но, вероятно, можно форкнуть
cloneLib
(npm install clone) и попытаться улучшить его, поскольку автор утверждает, что больше не поддерживает его.
Читайте также:
- 10 лайфхаков JavaScript, которые сделают из вас профессионала
- 4 функциональные концепции, которые следует знать каждому разработчику JavaScript
- Управление памятью JavaScript: как избежать утечек памяти и повысить производительность
Читайте нас в Telegram, VK и Дзен
Перевод статьи Tiago Bértolo: Which is the best method for deep cloning in Javascript? Are they all bad?