- 1 Использование Array.from() и Object.assign()
- 2 Клонировать объект или массив в JavaScript через спред (spread) оператор
- 3 Применение JSON.stringify() и JSON.parse()
- 4 Клонировать объект или массив в JavaScript вспомогательной функцией copy()
- 5 Тестирование производительности
- 6 Поддержка браузерами
Объекты и массивы в JavaScript являются изменяемыми. Это означает, что их состояние может быть изменено после их создания. Тем не менее, существует несколько способов клонировать объект или массив в JavaScript. В этом случае ссылки на них отличаются от ссылок на их исходные копии.
Они сравниваются по ссылкам, а не по значениям. Этим они отличаются от строковых и числовых типов данных. Давайте рассмотрим несколько методов правильного клонирования объектов и массивов. Кроме того, мы остановимся отдельно на лучшем способе копирования массива объектов.
Использование Array.from() и Object.assign()
Первый метод предназначен для клонирования массивов, а второй — объектов.
let arr = ['hello', 'world'];
let obj = { greeting: 'hi', name: 'universe' };
let arrClone = Array.from(arr);
let objClone = Object.assign({}, obj);
Стоит отметить, что такой вариант подходит для создания копий обычных (простых) массивов и объектов. Для вложенных или многомерных потребуется иной подход.
Методы Object.assign() и Array.from() создают неглубокие копии. Вложенные массивы или объекты также не клонируются. Вместо этого создаются ссылки на их оригинал.
let deepArr = [1, 2, ['a', 'b', 'c']];
let deepArrClone = Array.from(deepArr);
// Добавление элемента во вложенный массив
deepArrClone[2].push('d');
// Изменения затрагивают и оригинальный массив
console.log(deepArr[2]); // ['a', 'b', 'c', 'd']
Клонировать объект или массив в JavaScript через спред (spread) оператор
Для копии массива или объекта можно использовать и spread оператор.
let arr = ['hello', 'world'];
let obj = {
greeting: 'hi',
name: 'universe'
};
let arrClone = [...arr];
let objClone = {...obj};
Тем не менее, это приводит к аналогичным проблемам, как и при применении методов Array.from() и Object.assign(), когда речь заходит о вложенных или многомерных массивах и объектах.
let deepArr = [1, 2, ['a', 'b', 'c']];
let deepArrClone = [...deepArr];
// Добавление элемента во вложенный массив
deepArrClone[2].push('d');
// Изменения затрагивают и оригинальный массив
console.log(deepArr[2]); // ['a', 'b', 'c', 'd']
Применение JSON.stringify() и JSON.parse()
Один из наиболее что рекомендуемых способов решения проблемы копирования многомерных массивов и объектов — это структурировать их при помощи метода JSON.stringify(). Затем его необходимо преобразовать обратно методом JSON.parse().
let deepArr = [1, 2, ['a', 'b', 'c']];
let deepArrClone = JSON.parse(JSON.stringify(deepArr));
deepArrClone[2].push('d');
console.log(deepArr); // [1, 2, ['a', 'b', 'c']]
console.log(deepArrClone); // [1, 2, ['a', 'b', 'c', 'd']]
Несмотря на то, что это вполне рабочий вариант, у него есть и недостатки. Таким способом правильно клонируются только JSON-данные.
В качестве наглядного примера рассмотрим объект, включающий объекты с несколькими типами данных. Мы клонируем его с помощью методов JSON.stringify() и JSON.parse().
let obj = {
arr: [1, 2, 3, ['a', 'b', 'c']],
obj: {
greeting: 'hi',
name: 'world',
nums: [1, 2, 3],
details: {
age: 'old',
letters: ['a', 'b', 'c']
}
},
str: 'hi',
date: new Date(),
num: 1,
fn: function (nm) {
return `hi ${nm}!`;
},
reg: /test/i,
bool: true,
nl: null,
undef: undefined,
map: new Map([['hi', 'world'], ['hello', 'universe']]),
set: new Set(['hi', 'world'])
};
let objClone = JSON.parse(JSON.stringify(obj));
Массивы, объекты, строки, числа, логические значения и null скопировались без проблем.
Но конструктор new Date(), функции, регулярные выражения, методы Map() и Set() подверглись изменениям. Дата превратилась в строку, а остальные данные — в пустые объекты ({}).
let objClone = {
arr: [1, 2, 3, ['a', 'b', 'c']],
bool: true,
date: "2021-07-21T03:44:15.873Z",
map: {},
nl: null,
num: 1,
obj: {
greeting: "hi",
name: "world",
nums: [1, 2, 3],
details: {
age: 'old',
letters: ['a', 'b', 'c']
}
},
reg: {},
set: {},
str: "hi"
};
Если в исходном объекте или массиве используются только данные, которые являются валидными для JSON, то такой вариант копирования можно считать самым простым. В противном случае необходим иной подход.
Клонировать объект или массив в JavaScript вспомогательной функцией copy()
Давайте теперь разберёмся как клонировать объект или массив в JavaScript наиболее удобным способом. Для этого можно создать вспомогательную функцию. Назовём её copy(). Она будет перебирать каждый элемент в массиве или объекте, создавать новый объект этого типа и помещает в него все элементы из исходника. Когда она сталкивается с вложенными итерируемыми элементами, то повторяет весь этот процесс и с ними.
Результатом её работы является точная копия оригинала.
function copy (obj) {
function copyProps (clone) {
for (let key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
clone[key] = copy(obj[key]);
}
}
}
/**
* Создание иммутабельной копии объекта
* @return {Object}
*/
function cloneObj () {
let clone = {};
copyProps(clone);
return clone;
}
/**
* Создание иммутабельной копии массива
* @return {Array}
*/
function cloneArr () {
return obj.map(function (item) {
return copy(item);
});
}
/**
* Создание иммутабельной копии Map
* @return {Map}
*/
function cloneMap () {
let clone = new Map();
for (let [key, val] of obj) {
clone.set(key, copy(val));
}
return clone;
}
/**
* Создание иммутабельной копии Set
* @return {Set}
*/
function cloneSet () {
let clone = new Set();
for (let item of obj) {
clone.add(copy(item));
}
return clone;
}
/**
* Создание иммутабельной копии функции
* @return {Function}
*/
function cloneFunction () {
let clone = obj.bind(this);
copyProps(clone);
return clone;
}
// Получение типа объекта
let type = Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
// Возвращаем копию в зависимости от типа исходных данных
if (type === 'object') return cloneObj();
if (type === 'array') return cloneArr();
if (type === 'map') return cloneMap();
if (type === 'set') return cloneSet();
if (type === 'function') return cloneFunction();
return obj;
}
Результатом её работы является точная копия оригинала.
Тестирование производительности
Здесь следует отметить тот факт, что сочетание методов JSON.stringify() и JSON.parse() работает почти в два раза быстрее, чем вспомогательная функция copy().
Но это справедливо, когда речь заходит о небольших массивах и объектах, в том числе и с разными типами данных. Если требуется создать копию действительно большого массива или объекта, то в этом случае copy() превосходит первый вариант по скорости почти в три раза.
Типы данных | copy() | Методы работы с JSON |
Различные типы данных | 140.01 мс | 72.99 мс |
Массивы и объекты с большим объёмом данных | 643.22 мс | 1541.07 мс |
Безусловно, из этого можно сделать вывод, что использование методов Object.assign() и Array.from() наряду со spread оператором подойдёт для простых массивов и объектов.
При работе с многомерными объектами и массивами лучше прибегнуть к пользовательской функции copy(). Несмотря на то, что методы, используемые для JSON-данных в некоторых ситуациях работают быстрее, copy() гораздо более устойчива при клонировании больших объектов и массивов. Она особенно актуальна, когда речь заходит о приложениях с большими объёмами данных, которые хранятся в массивах или объектах, и которые со временем увеличиваются в объёмах.
Читайте также: Как проверить объект на пустоту в JavaScript.
Поддержка браузерами
В отличие от метода Object.assign(), который имеет широкую поддержку среди всех современных версий браузеров, Array.from() не работает в Internet Explorer.