Проникливі поради щодо створення та клонування масивів у JavaScript.

Дуже важливим аспектом кожної мови програмування є типи даних та структури, доступні в цій мові. Більшість мов програмування надають типи даних для представлення та роботи зі складними даними. Якщо ви працювали з такими мовами, як Python або Ruby, ви мали бачити типи даних, такі як списки , набори , кортежі , хеші , дикти тощо.
У JavaScript не так багато складних типів даних - у вас просто є масиви та об'єкти . Однак у ES6 до мови було додано кілька типів даних та структур, таких як символи , набори та карти .
Масиви в JavaScript - це подібні до списку об'єкти високого рівня із властивістю length та цілочисельними властивостями як індекси.У цій статті я поділяю пару хак для створення нових масивів JavaScript або клонування вже існуючих.
Створення масивів: конструктор масивів
Найпопулярнішим методом створення масивів є використання буквального синтаксису масиву , який є дуже простим. Однак, коли ви хочете динамічно створювати масиви, синтаксис літералу масиву не завжди може бути найкращим методом. Альтернативний метод - використання Array
конструктора.
Ось простий фрагмент коду, що демонструє використання Array
конструктора.
З попереднього фрагмента ми бачимо, що Array
конструктор створює масиви по-різному залежно від отриманих аргументів.
Нові масиви: із визначеною довжиною
Давайте детальніше розглянемо, що відбувається при створенні нової Array
заданої довжини. Конструктор просто встановлює length
властивість масиву на задану довжину, не встановлюючи ключів.

З наведеного фрагмента може виникнути спокуса думати, що для кожного ключа в масиві встановлено значення undefined
. Але реальність така, що ці клавіші ніколи не були встановлені (вони не існують).
Наступна ілюстрація робить це зрозумілішим:

Це робить марною спробу використовувати будь-який із методів ітерації масиву, наприклад map()
, filter()
або reduce()
маніпулювати ним. Скажімо, ми хочемо заповнити кожен індекс масиву числом 5
як значенням. Ми спробуємо наступне:

Ми бачимо, що тут map()
це не спрацювало, оскільки властивості індексу не існують у масиві - length
існує лише властивість.
Давайте розглянемо різні способи вирішення цієї проблеми.
1. Використання Array.prototype.fill ()
fill()
Метод заповнює всі елементи масиву від індексу початкового до кінцевого індексу зі значенням статичного. Кінцевий індекс не включається. Ви можете дізнатись більше про це fill()
тут.
Зверніть увагу, що це fill()
буде працювати лише у браузерах з підтримкою ES6.
Ось проста ілюстрація:

Тут ми змогли заповнити всі елементи створеного нами масиву 5
. За допомогою fill()
методу можна встановити будь-яке статичне значення для різних індексів масиву .
2. Використання Array.from ()
Array.from()
Метод створює новий, неглибоко-скопійований Array
екземпляр з масиву або як Iterable об'єкта. Ви можете дізнатись більше про це Array.from()
тут.
Зверніть увагу, що це Array.from()
буде працювати лише у браузерах з підтримкою ES6.
Ось проста ілюстрація:

Тут ми тепер маємо справжні undefined
значення, встановлені для кожного елемента масиву, що використовує Array.from()
. Це означає , що тепер ми можемо йти вперед і використовувати методи , як .map()
і .filter()
в масиві, так як властивості індексу в даний час існують.
Ще одне, на що варто звернути увагу, Array.from()
- це те, що він може приймати другий аргумент, який є функцією відображення. Він буде викликаний для кожного елемента масиву. Це робить зайвим дзвінок .map()
після Array.from()
.
Ось простий приклад:

3. Використання оператора розповсюдження
Оператор розповсюдження( ...
), доданий у ES6, може використовуватися для розповсюдження елементів масиву, встановлюючи відсутнім елементам значення undefined
. Це дасть той самий результат, що і просто виклик Array.from()
лише масиву як єдиного аргументу.
Ось проста ілюстрація того, як використовувати оператор поширення:

Ви можете продовжувати використовувати такі методи, як .map()
і .filter()
в масиві, оскільки властивості індексу тепер існують.
Використання Array.of ()
Подібно до того, як ми спостерігали при створенні нових масивів за допомогою Array
конструктора або функції, Array.of()
поводиться дуже подібним чином. Насправді, єдина різниця між Array.of()
і Array
полягає в тому, як вони обробляють один цілочисельний аргумент, переданий їм.
Поки Array.of(5)
створює новий масив з одним елементом, 5
і властивість length 1
, Array(5)
створює новий порожній масив з 5 порожніми слотами та властивістю length 5
.
var array1 = Array.of(5); // [5] var array2 = Array(5); // Array(5) {length: 5}
Крім цієї головної різниці, він Array.of()
поводиться так само, як Array
конструктор. Ви можете дізнатись більше про це Array.of()
тут.
Зверніть увагу, що це Array.of()
буде працювати лише у браузерах з підтримкою ES6.
Перетворення в масиви: схожі на масив та ітерабелі
Якщо ви пишете функції JavaScript досить довго, ви вже повинні знати про arguments
об'єкт - це масивноподібний об'єкт, доступний у кожній функції для зберігання списку аргументів, отриманих функцією. Хоча arguments
об’єкт схожий на масив, він не має доступу до Array.prototype
методів.
До ES6 зазвичай ви бачите фрагмент коду, як показано нижче, при спробі перетворити arguments
об'єкт на масив:

За Array.from()
допомогою оператора поширення або оператора розповсюдження ви можете зручно перетворити будь-який схожий на масив об’єкт у масив. Отже, замість цього:
var args = Array.prototype.slice.call(arguments);
Ви можете зробити будь-що з наведеного:
// Using Array.from() var args = Array.from(arguments); // Using the Spread operator var args = [...arguments];
Це також стосується ітерабелів, як показано на наступному малюнку:

Тематичне дослідження: Функція дальності
Як практичний приклад перед тим, як продовжити, ми створимо просту range()
функцію для реалізації нового вивченого нами масиву . Функція має такий підпис:
range(start: number, end: number, step: number) => Array
Ось фрагмент коду:
У цьому фрагменті коду ми використовували Array.from()
новий масив діапазону динамічної довжини, а потім заповнювали його послідовно збільшеними числами, надаючи функцію відображення.
Зверніть увагу, що наведений вище фрагмент коду не буде працювати для браузерів без підтримки ES6, за винятком випадків, коли ви використовуєте поліфіли.
Ось деякі результати виклику range()
функції, визначеної у наведеному вище фрагменті коду:

Ви можете отримати демо-версію коду, запустивши на Codepen наступну ручку :
Клонування масивів: виклик
У JavaScript масиви та об'єкти є посилальними типами. Це означає, що коли змінної присвоюється масив або об'єкт, те, що призначається змінній, є посиланням на місце в пам'яті, де зберігався масив або об'єкт.
Масиви, як і будь-який інший об'єкт у JavaScript, є посилальними типами. Це означає, що масиви копіюються за посиланням, а не за значенням.
Зберігання типів посилань таким чином має такі наслідки:
1. Подібні масиви не рівні.

Тут ми бачимо, що хоча array1
і array2
містять, здавалося б, однакові специфікації масиву, вони не рівні. Це пояснюється тим, що посилання на кожен з масивів вказує на інше місце в пам'яті.
2. Масиви копіюються за посиланням, а не за значенням.

Тут ми намагаємося скопіювати array1
в array2
, але в основному ми робимо вказівку array2
на те саме місце в пам'яті, на яке array1
вказує. Отже, обидва array1
і array2
вказують на одне і те ж місце в пам'яті і рівні.
Наслідком цього є те, що коли ми вносимо зміни до array2
, видаляючи останній елемент, останній елемент array1
також видаляється. Це пояснюється тим, що зміна фактично була внесена до масиву, що зберігається в пам'яті, тоді як array1
і array2
є лише вказівниками на те саме місце в пам'яті, де зберігається масив.
Клонування масивів: Хакі
1. Використання Array.prototype.slice ()
slice()
Метод створює неповну копію частини масиву , не зраджуючи масив. Ви можете дізнатись більше про це slice()
тут.
Фокус полягає у тому, щоб викликати slice()
або 0
як єдиний аргумент, або взагалі без аргументів:
// with O as only argument array.slice(0); // without argument array.slice();
Ось проста ілюстрація клонування масиву за допомогою slice()
:

Тут ви можете побачити, що array2
це клон array1
з однаковими елементами та довжиною. Однак вони вказують на різні місця в пам'яті, і як результат не рівні. Ви також помічаєте, що коли ми вносимо зміни до array2
, видаляючи останній елемент, array1
залишається незмінним.
2. Використання Array.prototype.concat ()
concat()
Метод використовується для об'єднання двох або більше масивів, в результаті чого новий масив, в той час як вихідні масиви залишаються незмінними. Ви можете дізнатись більше про це concat()
тут.
Фокус полягає у виклику concat()
або порожнього масиву ( []
) як аргументу, або взагалі без будь-яких аргументів:
// with an empty array array.concat([]); // without argument array.concat();
Клонування масиву за допомогою concat()
схоже на використання slice()
. Ось проста ілюстрація клонування масиву за допомогою concat()
:

3. Використання Array.from ()
Як ми бачили раніше, Array.from()
може бути використаний для створення нового масиву, який є мілкою копією вихідного масиву. Ось проста ілюстрація:

4. Використання деструктуризації масивів
З ES6 у нас є кілька більш потужних інструментів, таких як деструктуризація , поширенняоператор , функції стрілок тощо. Деструктуризація є дуже потужним інструментом для вилучення даних із складних типів, таких як масиви та об'єкти.
Фокус полягає у використанні техніки, яка називається параметрами відпочинку, яка передбачає поєднання деструктуризації масиву та оператора розповсюдження, як показано у наступному фрагменті:
let [...arrayClone] = originalArray;
Наведений фрагмент створює змінну з іменем, arrayClone
яка є клоном originalArray
. Ось проста ілюстрація клонування масиву за допомогою деструктуризації масиву:

Клонування: дрібне проти глибокого
Усі методи клонування масиву, які ми досліджували до цього часу, створюють неглибоку копію масиву. Це не буде проблемою, якщо масив містить лише примітивні значення. Однак, якщо масив містить вкладені посилання на об'єкти, ці посилання залишаться недоторканими, навіть коли масив клонований.
Ось дуже проста демонстрація цього:

Зверніть увагу, що зміна вкладеного масиву array1
також змінила вкладений масив у array2
і навпаки.
Рішення цієї проблеми полягає у створенні глибокої копії масиву, і це можна зробити декількома способами.
1. Техніка JSON
Найпростіший спосіб створити глибоку копію масиву - за допомогою комбінації JSON.stringify()
та JSON.parse()
.
JSON.stringify()
перетворює значення JavaScript у дійсний рядок JSON, тоді як JSON.parse()
перетворює рядок JSON у відповідне значення JavaScript або об'єкт.
Ось простий приклад:

Ці недоліки в техніці JSON можна в основному віднести до способу, яким JSON.stringify()
метод перетворює значення в рядок JSON.
Ось проста демонстрація цього недоліку при спробі JSON.stringify()
отримати значення, що містить вкладену функцію.

2. Помічник із глибокого копіювання
Життєздатною альтернативою техніці JSON буде впровадження власної допоміжної функції глибокого копіювання для клонування посилальних типів, незалежно від того, це масиви чи об’єкти.
Ось дуже проста і мінімалістична функція глибокого копіювання, яка називається deepClone
:
Зараз це не найкраща з функцій глибокого копіювання, як ви скоро побачите з деякими бібліотеками JavaScript - однак вона виконує глибоке копіювання в досить значній мірі.

3. Використання бібліотек JavaScript
Щойно визначена нами допоміжна функція глибокого копіювання недостатньо надійна для клонування всіх типів даних JavaScript, які можуть бути вкладені в складні об'єкти або масиви.
Бібліотеки JavaScript, такі як Lodash та jQuery, забезпечують більш надійні функції утиліти глибокого копіювання з підтримкою різних типів даних JavaScript.
Ось приклад, який використовує _.cloneDeep()
бібліотека Lodash:

Ось той самий приклад, але використання $.extend()
з бібліотеки jQuery:

Висновок
У цій статті ми змогли дослідити кілька методів для динамічного створення нових масивів та клонування вже існуючих, включаючи перетворення масивоподібних об’єктів та ітерацій у масиви.
Ми також бачили, як деякі нові функції та вдосконалення, введені в ES6, можуть дозволити нам ефективно виконувати певні маніпуляції з масивами.
Ми використовували такі функції, як деструктуризація та оператор поширення для клонування та розповсюдження масивів. Детальніше про деструктуризацію ви можете дізнатися з цієї статті.
Хлопай і слідуй
Якщо ви знайшли цю статтю проникливою, ви можете дати кілька оплесків, якщо не проти.
Ви також можете піти за мною на Medium (Радий Chinda) для отримання більш проникливих статей, які можуть вам виявитися корисними. Ви також можете стежити за мною у Twitter (@gladchinda).
Щасливого злому ...