- Простое клонирование/копирование массива в JavaScript
- Заключение
- Источники
- Как клонировать объект или массив в JavaScript
- Использование Array.from() и Object.assign()
- Клонировать объект или массив в JavaScript через спред (spread) оператор
- Применение JSON.stringify() и JSON.parse()
- Вспомогательная функция copy()
- Тестирование производительности
- Поддержка браузерами
- Создать независимую копию JavaScript массива :: Хранитель заметок
- Ещё заметки со схожей тематикой
- Копировать массив или объект JS по правилам
- Копировать массив или объект в JS не так просто как кажется
- Использовать функцию slice() для копирования массива
- Использовать Spread syntax чтобы копировать массив или объект
- Неглубокое копирование
- Лучший способ клонировать многомерный массив или объект
- Подводя итог
Простое клонирование/копирование массива в JavaScript
Чтобы клонировать/копировать содержимое массива, все, что вам нужно сделать, это вызвать метод slice, передав 0 в качестве первого аргумента:
Код выше создает копию исходного массива; имейте в виду, если в вашем массиве существуют объекты — они хранятся как ссылки; т.е. код выше не делает “deep” клонирование содержимого массива.
Чтобы добавить копирование, как нативный метод к массивам, вы могли бы сделать что-то вроде этого:
Array.prototype.clone = function () < return this.slice(0); >;
Давайте напишем рекурсивную функцию, чтобы копировать массив, который потенциально может иметь вложенные объекты или массивы:
function arrayClone (arr) < var i, copy; if (Array.isArray(arr)) < copy = arr.slice(0); for (i = 0; i < copy.length; i++) < copy[i] = arrayClone(copy[i]); >return copy; > else if (typeof arr === 'object') < throw 'Cannot clone array containing an object!'; >else < return arr; >>
Если вы используете Underscore – можете сделать это еще короче:
function arrayClone (arr) < if (_.isArray(arr)) < return _.map(arr, arrayClone); >else if (typeof arr === 'object') < throw 'Cannot clone array containing an object!'; >else < return arr; >>
С версии ECMAScript 2015 был введен метод Object.assign(), с его помощью глубокое клонирование можно реализовать еще проще:
var clone = Object.assign([], myArray);
Заключение
Если вам нужно глубокое копирование произвольно-вложенных объектов и/или массивов, крошечный node-clone прекрасно с этим справиться. Он будет правильно обрабатывать даже копирование объектов с циклическими ссылками!
Источники
Как клонировать объект или массив в 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 = ;
Тем не менее, это приводит к аналогичным проблемам, как и при применении методов 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[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 $!`; >, 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 , то такой вариант копирования можно считать самым простым. В противном случае необходим иной подход.
Вспомогательная функция copy()
Чтобы клонировать объект или массив наиболее удобным способом можно создать вспомогательную функцию. Назовём её copy() . Она будет перебирать каждый элемент в массиве или объекте, создавать новый объект этого типа и помещает в него все элементы из исходника. Когда она сталкивается с вложенными итерируемыми элементами, то повторяет весь этот процесс и с ними.
Результатом её работы является точная копия оригинала.
function copy (obj) < function copyProps (clone) < for (let key in obj) < if (Object.prototype.hasOwnProperty.call(obj, key)) < cloneJavascript скопировать массив объектов = copy(objJavascript скопировать массив объектов); >> > /** * Создание иммутабельной копии объекта * @return */ function cloneObj () < let clone = <>; copyProps(clone); return clone; > /** * Создание иммутабельной копии массива * @return */ function cloneArr () < return obj.map(function (item) < return copy(item); >); > /** * Создание иммутабельной копии Map * @return */ function cloneMap () < let clone = new Map(); for (let Javascript скопировать массив объектов of obj) < clone.set(key, copy(val)); >return clone; > /** * Создание иммутабельной копии Set * @return */ function cloneSet () < let clone = new Set(); for (let item of obj) < clone.add(copy(item)); >return clone; > /** * Создание иммутабельной копии функции * @return */ 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() превосходит первый вариант по скорости почти в 3 раза.
Типы данных | copy() | Методы работы с JSON |
---|---|---|
Различные типы данных | 140.01 мс | 72.99 мс |
Массивы и объекты с большим объёмом данных | 643.22 мс | 1541.07 мс |
Поддержка браузерами
Метод Object.assign() имеет широкую поддержку среди всех современных версий браузеров. В отличие от него Array.from() не работает в IE .
Создать независимую копию JavaScript массива :: Хранитель заметок
В JavaScript все присваивания объектов реализуются через передачу ссылок на них.
var oldArray = ["a", "b", "c"]; var newArray = oldArray;
Хоть мы и получили две разные переменные, но тем не менее они обе ссылаются на один и тот же объект массива. Если сейчас в одном массиве произвести какие-либо манипуляции с элементами, то аналогичные изменения можно будет увидеть и в другом.
Если вы хотите сделать независимую копию массива, то нужно использовать метод slice без аргументов.
var oldArray = ["a", "b", "c"]; var newArray = oldArray.slice();
Массивы oldArray и newArray будут состоять из одних и тех же элементов, но фактически это будут разные объекты.
Важно запомнить, что если массив состоит из сущностей Array или Object, то они по прежнему будут ссылаться на родительские объекты.
Ещё заметки со схожей тематикой
Категории
Коментарии к заметке
slice реально очень хорошо помагает если надо скопировать массива. При этом, если измениь первый массив, то второй останется без изменений.
Спасибо, искал долго, было много всего не по теме, а у вас сразу за пол странички все объяснили. Спасибо большое!
Атлант, тогда у этой задачи нет универсального решения. Объекты можно «клонировать» с помощью метода Object.assign() (описание Object.assign() на MDN).
var target = Object.assign(<>, source);
Но у этого метода как и slice() та же проблема — копируются только ссылки на вложенные объекты и массивы. Чтобы сделать глубокую копию всех данных, вам нужно рекурсивно вызывать соответствующие методы клонирования. В некоторых JS-библиотеках эти алгоритмы уже реализованы. Можете воспользоваться cloneDeep из библиотеки Lodash. А универсального решения задачи тут нет из-за того, что в ваших массивах и объектах могут храниться такие значения, для которых сложно или даже невозможно создать независимую копию, например, функции, DOM-узлы или объекты WeakMap.
var newArray = JSON.parse(JSON.stringify(oldArray))
Чуть мозг не поломал, пока не понял, что ссылки на объекты у скопированного массива действительно остаются прежними, спасибо за этот комментарий.
© 2009–2023 Владимир Кузнецов.
Все права защищены и принадлежат их владельцам.
Копирование материалов данного блога допускается только с разрешения автора.
Копировать массив или объект JS по правилам
Подробный разбор правильного копирования массивов и объектов с примерами кода на JavaScript.
Как только вы решили копировать массив или объект в JavaScript следует вспомнить о том, что массивы и объекты являются изменяемыми(mutable), в отличие от примитивных переменных и хранятся как ссылки.
Изменяемые, это те, состояние которых может быть изменено после их создания.
На практике это будет означать следующее:
const sheeps = ['🐑', '🐑'] const sheeps2 = sheeps sheeps2.push('🐺') console.log(sheeps2) // [ '🐑', '🐑', '🐺' ] // Аааа 😱, оригинальный массив sheeps изменился?! console.log(sheeps) // [ '🐑', '🐑', '🐺' ]
Поэтому необходимо создавать копию данных, а не делать ссылку на существующие.
Копировать массив или объект в JS не так просто как кажется
Для этого необходимо не просто использовать знак равенства для присвоения новой переменной старых значений, а производить клонирование, иначе вы просто создадите ссылку и будете работать с исходным объектом.
Использовать функцию slice() для копирования массива
Эта функция используется для копирования части массива. Если не указывать параметры, то массив будет копироваться целиком.
const sheeps = ['🐑', '🐑', '🐑'] const cloneSheeps = sheeps.slice()
Другой способ с использованием Array.from :
const cloneSheeps = Array.from(sheeps)
Если бы sheeps был бы объектом, то копировать следовало бы так:
const cloneSheeps = <> cloneSheeps.prototype = sheeps.prototype
let cloneSheeps = Object.assign(<>, sheeps)
Использовать Spread syntax чтобы копировать массив или объект
Spread syntax, появившийся в ES6, позволяет «вытаскивать» перебираемые элементы из своего контейнера.
const sheeps = ['🐑', '🐑', '🐑']; const fakeSheeps = sheeps; // три точки - это и есть spread operator const cloneSheeps = [. sheeps]; console.log(sheeps === fakeSheeps); // true --> это указывает на тот же кусок в памяти console.log(sheeps === cloneSheeps); // false --> это указывает на новый кусок в памяти
Неглубокое копирование
Обратите внимание на то, что происходит клонирование только одного уровня — поверхностное копирование!
Если у вас многомерный массив для создания копии каждого уровня придется применить один из предыдущих способов к каждому уровню отдельно.
let sheeps = [['🐑', '🐑'], ['🐑', '🐑'], ['🐑', '🐑']] let cloneSheeps = [] for (let i = 0; i < sheeps.length; i++) cloneSheeps.push(sheeps[i].slice()) cloneSheeps[1].push('🐺') console.log(sheeps) console.log(cloneSheeps)
Или с помощью функции .map() :
let sheeps = [['🐑', '🐑'], ['🐑', '🐑'], ['🐑', '🐑']] let cloneSheeps = sheeps.map(function (item) < return [. item] >) cloneSheeps[1].push('🐺') console.log(sheeps) console.log(cloneSheeps)
Если у вас смешанные данные, например, когда массив содержит объекты, в которых значениями могут быть тоже объекты или массивы, то следует использовать рекурсивную функцию, в которой вы будете проверять тип данных и в зависимости от этого копировать массив, объект или примитивную переменную.
function deepCopy (obj) < if ('object' === typeof obj) < if (obj instanceof Array) < let length = obj.length let newObj = new Array(length) for (let i = 0; i < length; i++) < newObj[i] = (deepCopy(obj[i])) >return newObj > else < let newObj = <>if (obj.prototype) < newObj.prototype = obj.prototype >for (let key in obj) < newObjJavascript скопировать массив объектов = deepCopy(objJavascript скопировать массив объектов) >return newObj > > return obj >
Лучший способ клонировать многомерный массив или объект
На данный момент наиболее простым способом клонирования массивов и объектов является преобразование данных в строку, а за тем обратное преобразование в объект с помощью JSON :
let cloneSheeps = JSON.parse(JSON.stringify(sheeps))
Подводя итог
- Помните — объекты Javascript изменчивы и хранятся в памяти по ссылке;
- Все классические методы клонирования или Spread syntax осуществляют поверхностное копирование(на одном уровне);
- JSON.stringify и JSON.parse можно использовать для глубокого копирования;
- Можно создать свою собственную функцию для глубокого копирования или использовать сторонние библиотеки, такие как Lodash, Underscore и тп.
Статья подготовлена по материалам следующих источников:
( 1 ) ( 0 )