Поверхнева копія масиву
Поверхнева копія (shallow copy) масиву, це новий масив з власним посиланням, у який скопійовано елементи верхнього рівня. Примітиви потрапляють у копію за значенням, а вкладені об'єкти та масиви, за посиланням, тому вони залишаються спільними з оригіналом.
Теорія
TL;DR
- Поверхнева копія створює новий масив, тому
copy === arrдаєfalse, а зміни в копії не чіпають оригінал на верхньому рівні. - Найкоротший спосіб, spread:
const copy = [...arr]. - Те саме роблять
arr.slice(),Array.from(arr)і[].concat(arr). - Усі вони копіюють лише верхній рівень: вкладені об'єкти й масиви лишаються спільними.
- Заміна цілого елемента копії безпечна, зміна поля всередині вкладеного об'єкта, ні.
- Для глибокої копії потрібен
structuredClone(arr)абоJSON.parse(JSON.stringify(arr)).
Швидкий приклад
const arr = [1, 2, 3];
const copy = [...arr];
console.log(copy); // [1, 2, 3]
console.log(copy === arr); // false (це новий масив)Spread оператор
Оператор розпакування ... розкладає елементи масиву в новий літерал. Це найсучасніший і найкоротший запис, і саме його очікують почути на співбесіді.
const arr = [1, 2, 3];
const copy = [...arr];
console.log(copy); // [1, 2, 3]
console.log(copy === arr); // false (новий масив)Копія при цьому поверхнева: якщо елементами були об'єкти, вони й далі посилаються на ті самі дані.
const arr = [{ name: 'Tim' }];
const copy = [...arr];
copy[0].name = 'Alex';
console.log(arr[0].name); // "Alex", бо об'єкт спільнийslice(), Array.from() і concat()
slice() без аргументів повертає новий масив, копію всього вихідного. Спосіб старий, але цілком надійний:
const arr = [1, 2, 3];
const copy = arr.slice();
console.log(copy); // [1, 2, 3]Array.from() створює новий масив з ітерованого або масивоподібного значення:
const arr = [1, 2, 3];
const copy = Array.from(arr);
console.log(copy); // [1, 2, 3]Він корисний, коли потрібна не просто копія, а копія з перетворенням кожного елемента:
const doubled = Array.from(arr, x => x * 2); // [2, 4, 6]concat() працює тому, що повертає новий масив, а не змінює вихідний:
const arr = [1, 2, 3];
const copy = [].concat(arr);
console.log(copy); // [1, 2, 3]Що залишається спільним
Усі перелічені методи копіюють тільки верхній рівень. Якщо в масиві лежать об'єкти або вкладені масиви, вони залишаються за посиланням.
const arr = [[1, 2], [3, 4]];
const copy = [...arr];
copy[0][0] = 99;
console.log(arr[0][0]); // 99, бо вкладений масив спільнийНатомість заміна цілого елемента копії оригінал не зачіпає, бо змінюється слот нового масиву, а не спільний об'єкт:
copy[0] = [7, 8];
console.log(arr[0]); // [99, 2], оригінал не змінивсяПорівняння способів
| Спосіб | Код | Створює новий масив | Глибока копія |
|---|---|---|---|
| Spread | [...arr] | Так | Ні |
slice() | arr.slice() | Так | Ні |
Array.from() | Array.from(arr) | Так | Ні |
concat() | [].concat(arr) | Так | Ні |
Підсумок простий: щоб зробити поверхневу копію масиву, пишіть const copy = [...arr];.
Глибоке копіювання
Коли потрібно скопіювати ще й вкладені структури, поверхневої копії замало:
const deep = structuredClone(arr);structuredClone() вбудований у сучасні браузери і Node.js, коректно обробляє Map, Set, Date, типізовані масиви та циклічні посилання, але не вміє копіювати функції. Старіший прийом JSON.parse(JSON.stringify(arr)) простіший, проте втрачає undefined і функції, перетворює Date на рядок і кидає помилку на циклічних посиланнях.
Типові помилки
- Вважати, що
[...arr]дає повністю незалежний масив: вкладені об'єкти лишаються спільними. - Плутати копіювання з присвоєнням:
const copy = arrне створює масив, це те саме посилання. - Брати
JSON.parse(JSON.stringify(arr))для масиву зDateчи функціями і дивуватися втраченим даним. - Думати, що
slice()змінює масив: він повертає новий, на відміну відsplice(), який змінює вихідний. - Копіювати масив ручним циклом з
push()там, де достатньо одного виразу зі spread.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.