Хакове за създаване на JavaScript масиви

Прозорливи съвети за създаване и клониране на масиви в JavaScript.

Много важен аспект на всеки език за програмиране са наличните типове данни и структури в езика. Повечето езици за програмиране предоставят типове данни за представяне и работа със сложни данни. Ако сте работили с езици като Python или Ruby, трябва да сте видели типове данни като списъци , набори , кортежи , хешове , диктове и т.н.

В JavaScript няма толкова много сложни типове данни - просто имате масиви и обекти . В ES6 обаче към езика бяха добавени няколко типа данни и структури, като символи , набори и карти .

Масивите в JavaScript са подобни на списък обекти на високо ниво със свойство дължина и цели числа като индекси.

В тази статия споделям няколко хака за създаване на нови масиви на 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и свойство за дължина на 1, Array(5)създава нов празен масив с 5 празни слота и свойство за дължина на 5.

var array1 = Array.of(5); // [5] var array2 = Array(5); // Array(5) {length: 5}

Освен тази основна разлика, се Array.of()държи точно като Arrayконструктора. Можете да научите повече за това Array.of()тук.

Имайте предвид, че Array.of()ще работи само в браузъри с поддръжка на ES6.

Преобразуване в масиви: Харесвания на масиви и Iterables

Ако сте писали 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, освен ако използвате polyfills.

Ето някои резултати от извикването на 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 техниката могат да бъдат главно приписани на начина, по който JSON.stringify()методът преобразува стойности в JSON низ.

Ето една проста демонстрация на този недостатък при опит за JSON.stringify()стойност, съдържаща вложена функция.

2. Помощник за дълбоко копиране

Жизнеспособна алтернатива на техниката JSON ще бъде да внедрите вашата собствена помощна функция за дълбоко копиране за клониране на референтни типове, независимо дали са масиви или обекти.

Ето една много проста и минималистична функция за дълбоко копиране, наречена deepClone:

Сега това не е най-доброто от функциите за дълбоко копиране, както скоро ще видите с някои библиотеки на JavaScript - обаче, той извършва дълбоко копиране в доста добра степен.

3. Използване на JavaScript библиотеки

Току-що дефинираната помощна функция за дълбоко копиране не е достатъчно стабилна при клонирането на всички видове данни на JavaScript, които могат да бъдат вложени в сложни обекти или масиви.

JavaScript библиотеките като Lodash и jQuery предоставят по-надеждни функции на дълбокото копиране с поддръжка за различни видове JavaScript данни.

Ето пример, който използва _.cloneDeep()от библиотеката Lodash:

Ето същия пример, но с използване $.extend()от библиотеката jQuery:

Заключение

В тази статия успяхме да изследваме няколко техники за динамично създаване на нови масиви и клониране на вече съществуващи, включително преобразуване на подобни на масиви обекти и итерации в масиви.

Също така видяхме как някои от новите функции и подобрения, въведени в ES6, могат да ни позволят ефективно да извършваме определени манипулации върху масиви.

Използвахме функции като деструктуриране и оператор за разпространение за клониране и разпространение на масиви. Можете да научите повече за деструктурирането от тази статия.

Пляскайте и следвайте

Ако сте намерили тази статия проницателна, можете да дадете няколко аплодисмента, ако нямате нищо против.

Можете също така да ме следвате в Medium (Glad Chinda) за по-проницателни статии, които може да ви помогнат. Можете също да ме следвате в Twitter (@gladchinda).

Честит хакерство ...