Пора заняться серьезными делами. Суперсерьезными! В последних нескольких главах мы изучили разные значения, в том числе: строки (текст), числа, логические значения (true и false), функции и другие встроенные элементы JavaScript.
Вот некоторые примеры, чтобы освежить память:
let someText = "hello, world!";
let count = 50;
let isActive = true;
В отличие от других языков, JavaScript упрощает определение и использование этих встроенных элементов. Нам даже не требуется составлять план их будущего использования. Но несмотря на всю простоту, существует множество скрытых деталей. И их знание важно, так как не только облегчает понимание кода, но и ускоряет выявление причин его неисправностей.
Как вы могли предположить, встроенные элементы — не самый удачный способ описания различных значений, используемых в JS. Существует более официальное имя для таких значений, а именно типы. В этой главе мы начнем плавное знакомство с их сутью и назначением.
Поехали!
Сначала поговорим о пицце
Поскольку я постоянно что-нибудь ем (или думаю, что бы съесть), то постараюсь объяснить загадочный мир типов на более простом примере — мире пиццы.
Если вы давненько ее не ели, то напомню, как она выглядит:
Конечно же, пицца не появляется в таком виде из ниоткуда. Она создается из простых и сложных ингредиентов:
Простые ингредиенты легко выявить. Это грибы и халапеньо. Причина, по которой мы называет их простыми, в том, что их нельзя разложить на составные части:
Они не изготавливаются и не составляются из других компонентов.
К сложным же ингредиентам относятся сыр, соус, основа из теста и пеперони. Сложными их делает то, что они сделаны из других ингредиентов:
К сожалению, такие ингредиенты, как сыр и пеперони, не бывают простыми. Для их приготовления нужно смешивать, жарить и добавлять различные компоненты. Кроме того, их получение не ограничивается смешиванием простых ингредиентов, но может также требовать совмещения сложных.
От пиццы к JavaScript
Все, что мы узнали о пицце в предыдущем разделе, было неспроста. Описание простых и сложных ингредиентов вполне применимо к типам в JavaScript. Каждый отдельно взятый ингредиент можно рассматривать как аналог типа, который вы можете использовать (рис. 12.1).
Рис. 12.1. Список простых типов JavaScript
Подобно сыру, соусу, пеперони, грибам и бекону в нашей пицце, типами в JavaScript являются string (строка), number (число), boolean (логическое значение), null (пустой), undefined (не определен), bigint (целочисленные значения), symbol (символы) и Object (объект). С некоторыми из этих типов вы уже можете быть знакомы, с некоторыми — нет. Подробнее мы будем рассматривать их в дальнейшем, сейчас же в табл. 12.1 вы можете посмотреть краткое описание их назначения.
Как мы видим, каждый тип имеет свое уникальное назначение. При этом они, аналогично ингредиентам пиццы, также подразделяются на простые и сложные. Только в терминологии JavaScript простые и сложные типы называются примитивами (примитивные типы) и объектами (объектные типы) соответственно.
К примитивным типам относятся string, number, boolean, null, bigint, symbol и undefined. Любые значения, попадающие в их юрисдикцию, не подлежат делению на части. Они являются халапеньо и грибами в мире JavaScript. Примитивы достаточно легко определять и оформлять в понятные элементы. В них нет глубины, и при встрече с ними мы, как правило, получаем то, что видим изначально.
Табл. 12.1. Типы
Тип
Назначение
string
Основная структура для работы с текстом
number
Позволяет работать с числами
boolean
Используется там, где нужно получить true или false
null
Предстает цифровым эквивалентом ничего
undefined
Похожий по смыслу на null. Возвращается, когда значение подразумевается, но на деле отсутствует. Например, если вы объявляете переменную, но ничего ей не присваиваете
bigint
Позволяет работать с крайне большими и малыми числами, выходящими за пределы возможностей обычного типа number
symbol
Нечто уникальное и неизменяемое, то, что при желании можно использовать как идентификатор свойств объекта
Object
Выступает в роли оболочки для других типов, включая другие объекты
Объектные же типы, представленные как Object в вышеприведенной таблице, оказываются более загадочными. Поэтому, прежде чем перейти к описанию деталей всех перечисленных типов, стоит отдельно рассмотреть, чем именно являются объекты.
Что такое объект?
Принцип объектов в таких языках программирования, как JavaScript, прекрасно отражает их аналогию из реальной жизни, в которой мы все буквально окружены объектами. К ним относятся ваш компьютер, книга на полке, картошка (спорно), будильник, плакат, заказанный на eBay, и т. д. Продолжать можно бесконечно.
Некоторые объекты вроде пресс-папье малофункциональны и могут долго бездействовать.
Другие объекты, вроде телевизора, уже выходят за рамки простого существования и выполняют множество задач:
Обычный телевизор получает сигнал, позволяет вам включать его и выключать, щелкать каналы, регулировать громкость и прочее.
Здесь важно понять, что объекты имеют разную форму, размер и назначение. Несмотря на эти отличия, на верхнем уровне они все одинаковы и представляют собой абстракцию. Они дают возможность пользоваться ими, не задаваясь вопросом об их внутреннем устройстве. Даже простейшие объекты скрывают в себе определенный уровень сложности, о котором можно не париться.
Например, не важно, что именно происходит внутри телека, как спаяны провода или какой клей использовался для соединения деталей. Все это не имеет значения. Все, что вас интересует, так это чтобы телевизор выполнял свое предназначение. Он должен исправно переключать каналы, позволять регулировать громкость и пр. Остальное — лишние заморочки.
В принципе, объект можно рассматривать как черный ящик. Существует ряд предопределенных/описанных действий, которые он совершает. Увидеть же, как он это делает, достаточно непросто. На деле вас это и не интересует до тех пор, пока он делает все как надо. Мы изменим свое представление об этом позже, когда поучимся создавать внутренности объекта, а пока насладимся простотой этого мира.
Предопределенные объекты в JavaScript
Помимо встроенных типов, перечисленных ранее, в JS также изначально присутствуют предопределенные объекты. Эти объекты позволяют работать с чем угодно, включая наборы данных, даты, текст и числа. В табл. 12.2 приводится аналогичный предыдущему список, описывающий их назначения:
Табл. 12.2. Объекты
Тип
Назначение
Array
Помогает хранить, извлекать и манипулировать наборами данных
Boolean
Служит оболочкой примитива Boolean, а также работает посредством значений true и false
Date
Упрощает работу с датами и их представление
Function
Позволяет вызывать заданный код
Math
Умник среди типов, расширяющий возможности работы с числами
Number
Служит оболочкой примитива number
RegExp
Предоставляет богатые возможности сопоставления текстовых шаблонов
String
Служит оболочкой примитива string
Использование встроенных объектов несколько отличается от использования примитивов. Каждый объект в этом плане по-своему особенный. Подробное пояснение всех этих особенностей использования я отложу на потом, а здесь приведу короткий фрагмент кода c комментарием, который покажет возможные варианты:
// массив
let names = ["Jerry", "Elaine", "George", "Kramer"];
let alsoNames = new Array("Dennis", "Frank", "Dee", "Mac");
// округленное число
let roundNumber = Math.round("3.14");
// текущая дата
let today = new Date();
// объект boolean
let booleanObject = new Boolean(true);
// бесконечность
let unquantifiablyBigNumber = Number.POSITIVE_INFINITY;
// объект string
let hello = new String("Hello!");
Вас может несколько озадачить то, что примитивы string, boolean, symbol, bigint и number могут существовать и в форме объектов. Внешне эта объектная форма выглядит очень похожей на примитивную. Вот пример:
let movie = "Pulp Fiction";
let movieObj = new String("Pulp Fiction");
console.log(movie);
console.log(movieObj);
При выводе обоих вариантах вы увидите одинаковый результат. Тем не менее внутренне movie и movieObj весьма различны. Первый буквально является примитивом типа string, а второй имеет тип Object. Это ведет к интересному (а иногда и непонятному) поведению, о котором я постепенно расскажу в процессе изучения встроенных типов.
КОРОТКО О ГЛАВНОМ
Если вам кажется, что все оборвалось на самом интересном месте, то это вполне нормально. Главный вывод здесь в том, что примитивы составляют большинство основных типов, которые вы будете использовать. Объекты несколько сложнее и состоят из примитивов или других объектов. Мы узнаем обо всем этом больше, когда начнем углубляться в тему. Помимо прочего, мы также узнали имена встроенных типов и некоторые присущие им особенности.
В последующих главах мы глубже изучим все эти типы, а также связанные с их использованием нюансы. Рассматривайте эту главу как плавный разгон, после которого вы резко влетите на рельсы безумных американских горок.
Глава 13. Массивы
Давайте представим, что вы хотите составить список на листке бумаги. Назовем его продукты. Теперь запишите в нем пронумерованный список, начинающийся с нуля, и перечислите все, что вам нужно (рис. 13.1).
Рис. 13.1. Список продуктов
Написав простой список, вы получили пример массива из реальной жизни. Листок бумаги, проименованный как продукты, это и есть ваш массив. Предметы же, которые вы хотите купить, — это значения массива.
В этом уроке вы не только узнаете, какие продукты я предпочитаю покупать, но и познакомитесь с очень распространенным типом — массивом.
Поехали!
Создание массива
Сейчас для создания массивов крутые чуваки используют открывающиеся и закрывающиеся квадратные скобки. Ниже приведена переменная groceries (продукты), инициализированная как пустой массив:
let groceries = [];
Такой скобочный способ создания массива больше известен как литеральная нотация массива.
Как правило, вы будете создавать массив, изначально содержащий определенные элементы. Для этого просто поместите нужные элементы в скобки и разделите их запятыми:
let groceries = ["Milk", "Eggs", "Frosted Flakes", "Salami", "Juice"];
Обратите внимание, что теперь массив содержит Milk (молоко), Eggs (яйца), Frosted Flakes (глазированные хлопья), Salami (салями) и Juice (сок). Считаю необходимым напомнить о важности запятых.
Теперь, когда вы научились объявлять массив, давайте взглянем на то, как его можно использовать для хранения данных и работы с ними.
Обращение к значениям массива
Одна из прелестей массивов в том, что вы имеете легкий доступ не только к ним самим, но и к их значениям, аналогично выделению одного из продуктов в вашем списке (рис. 13.2).
Рис. 13.2. Массивы позволяют выборочно обращаться к отдельным элементам
Для этого вам достаточно знать простую процедуру обращения к отдельному элементу.
Внутри массива каждому элементу присвоен номер, начиная с нуля. На рис. 13.2 Milk имеет значение 0, Eggs — 1, FrostedFlakes соответствует значение 2 и т. д. Формально эти номера называются значением индекса (индексами).
В данном случае наш массив groceries объявлен следующим образом:
let groceries = ["Milk", "Eggs", "Frosted Flakes", "Salami", "Juice"];
Если мне понадобится обратиться к одному из элементов, то все, что потребуется, — это передать значение его индекса:
groceries[1]
Значение индекса передается массиву внутри квадратных скобок. В текущем примере мы обращаемся к значению Eggs, так как именно этому элементу соответствует позиция индекса 1. Если передать 2, то вернется FrostedFlakes. Вы можете продолжать передавать значения индекса, пока они не закончатся.
Диапазон чисел, которые вы можете использовать в качестве значений индекса, на одно меньше, чем длина самого массива. Причина в том, что индексы начинаются с 0. Если в массиве есть пять элементов, то попытка отобразить grocery[6] или grocery[5] приведет к появлению сообщения undefined.
Идем дальше. В большинстве реальных сценариев вам понадобится программно перебирать весь массив вместо обращения к каждому элементу отдельно.
Для осуществления этого вы можете использовать цикл for:
for (let i = 0; i < groceries.length; i++) {
let item = groceries[i];
}
Помните, что диапазон цикла начинается с 0 и заканчивается на одно значение раньше полной длины массива (возвращаемой как свойство length). Все работает именно так по уже описанной мной причине — значения индекса начинаются с 0 и заканчиваются на одно значение раньше, чем возвращаемая длина массива. При этом свойство length возвращает точное число элементов.
Добавление элементов
Ваши массивы будут редко сохранять свое изначальное состояние, так как вы, скорее всего, будете добавлять в них элементы. Для этого используется метод push:
groceries.push("Cookies");
Метод push вызывается непосредственно для массива, при этом в него передаются добавляемые данные. В итоге вновь добавленные элементы всегда оказываются в конце массива.
Например, если выполнить этот код для изначального массива, вы увидите, что элемент Cookies (печенье) добавлен в его конец (рис. 13.3).
Рис. 13.3. Теперь массив расширен добавленным в конец элементом Cookies
Если же вы хотите добавить данные в начало, используйте метод unshift:
groceries.unshift("Bananas");
При добавлении данных в начало массива значение индекса каждого из существующих в нем элементов увеличивается с учетом вновь появившихся данных (рис. 13.4).
Рис. 13.4. Только что добавленный элемент вставлен в начало
Причина в том, что первый элемент массива всегда будет иметь значение индекса 0. Поэтому элемент, изначально занимающий позицию значения 0, вынужденно смещается, смещая и все следующие за ним элементы, освобождая тем самым место для добавляемых данных.
При использовании методы push и unshift помимо добавления элементов также возвращают новую длину массива:
console.log(groceries.push("Cookies")); // возвращает 6
Я не уверен, полезно ли это, но на всякий случай имейте это в виду.
Удаление элементов
Для удаления можно использовать методы pop и shift. Pop удаляет последний элемент и возвращает его:
let lastItem = groceries.pop();
Метод shift делает то же самое, но с обратной стороны массива, то есть вместо удаления и возвращения последнего элемента он проделывает это с первым:
let firstItem = groceries.shift();
При удалении элемента из начала массива позиции индексов остальных уменьшаются на 1, заполняя тем самым появившийся пропуск (рис. 13.5).
Рис. 13.5. Что происходит при удалении элементов из массива
Обратите внимание, что при добавлении элементов с помощью unshift или push значение, возвращаемое при вызове этих методов, является новой длиной массива. Но при использовании методов pop или shift происходит не то же самое. В данном случае при удалении элементов значение, возвращаемое при вызове метода, является самим удаляемым элементом.
Поиск элементов в массиве
Для поиска элементов внутри массива существует несколько методов: indexOf, lastIndexOf, includes, find, findIndex и filter. Во избежание усложнения мы пока что сконцентрируемся на indexOf и lastIndexOf. Работа этих двух индексов заключается в сканировании массива и возвращении индекса совпадающего элемента.
Метод indexOf возвращает первый найденный индекс искомого элемента:
let groceries =["Milk", "Eggs", "Frosted Flakes", "Salami", "Juice"];
let resultIndex = groceries.indexOf("Eggs",0);
console.log(resultIndex); // 1
Обратите внимание, что переменная resultIndex содержит результат вызова indexOf для массива groceries. Для использования indexOf я передаю ему искомый элемент вместе с индексом, с которого следует начать:
groceries.indexOf("Eggs", 0);
В данном случае indexOf вернет значение 1.
Метод lastIndexOf похож на indexOf в использовании, но отличается тем, что возвращает при обнаружении элемента. Если indexOf находит первый индекс искомого элемента, то lastIndexOf находит и возвращает последний индекс этого элемента.
Если же искомый элемент в массиве отсутствует, оба этих метода возвращают -1.
Слияние массивов
Последнее, что мы рассмотрим, — это слияние двух раздельных массивов для создания нового. Предположим, у вас есть два массива good (хорошие) и bad (плохие):
let good = ["Mario", "Luigi", "Kirby", "Yoshi"];
let bad = ["Bowser", "Koopa Troopa", "Goomba"];
Чтобы совместить их, используйте метод concat для массива, который вы хотите расширить, и передайте в него второй массив в виде аргумента. В итоге будет возвращен новый массив, содержащий и good, и bad:
let goodAndBad = good.concat(bad);
console.log(goodAndBad);
В этом примере метод concat возвращает новый массив, поэтому переменная goodAndBad становится массивом, содержащим результат произведенной конкатенации. Первыми в новом массиве идут элементы good, а затем элементы bad.
Отображение, фильтрация и сокращение массивов
До сих пор мы рассматривали различные способы добавления элементов, их удаления и другие счетные операции. Помимо этого, массивы предлагают простые способы управления содержащимися в них данными. Эти простые способы представлены методами map (отображение), reduce (сокращение) и filter (фильтрация).
Консервативный способ
Прежде чем говорить о map, reduce и filter и предлагаемом ими удобстве обращения с данными, давайте рассмотрим не самый удобный подход. При этом подходе вы традиционно используете цикл for и отслеживаете свое местонахождение в массиве, испытывая при этом, мягко говоря, не самые приятные чувства.
Для наглядности давайте рассмотрим следующий массив имен:
let names = ["marge", "homer", "bart", "lisa", "maggie"];
Этот соответствующим образом названный массив names содержит список имен, написанных в нижнем регистре. Мы же хотим исправить это, сделав первую букву каждого из них заглавной. С помощью цикла for это можно сделать так:
let names = ["marge", "homer", "bart", "lisa", "maggie"];
let newNames = [];
for (let i = 0; i < names.length; i++) {
let name = names[i];
let firstLetter = name.charAt(0). toUpperCase();
newNames.push(firstLetter + name.slice(1));
}
console.log(newNames);
Обратите внимание, что мы перебираем каждый элемент, делаем первую букву заглавной и добавляем исправленное имя в новый массив newNames. Здесь нет ничего магического или сложного, но вы будете часто брать элементы массива, изменять их (или обращаться к ним) и возвращать новый массив с измененными данными. Это достаточно тривиальная задача, где задействуется много рутинного повторяющегося кода. В больших кодовых базах разбор происходящего в цикле добавляет ненужные хлопоты. Вот почему были введены методы map, filter и reduce. С их помощью вы получаете все возможности цикла for без ненужных побочных эффектов и лишнего кода. Кому бы это не понравилось?
Изменение каждого элемента с помощью map
Начнем с метода map, который мы используем для модификации всех элементов массива во что-либо другое, представленное в виде нового массива (рис. 13.6).
Рис. 13.6. Оригинальный и новый массивы
Используется map следующим образом:
let newArray = originalArray.map(someFunction);
Эта единственная строка выглядит приятно и располагающе, но изнутри является весьма сложной. Давайте с этим разберемся. Работает метод map так: вы вызываете его для массива, на который хотите воздействовать (originalArray), и передаете ему функцию (someFunction) в качестве аргумента. Функция будет выполняться для каждого элемента массива, то есть вы изначально сможете написать код для изменения всех этих элементов на ваше усмотрение. В конечном итоге вы получите новый массив, содержащий данные, полученные после выполнения функции someFunction для элементов оригинального массива. Звучит просто, не правда ли?
Теперь, вооружившись map, давайте вернемся к нашей предыдущей задаче по изменению первых букв имен в массиве на заглавные. Сначала взглянем на весь код целиком, а затем рассмотрим важные детали.
let names = ["marge", "homer", "bart", "lisa", "maggie"];
function capitalizeItUp(item) {
let firstLetter = item.charAt(0). toUpperCase();
return firstLetter + item.slice(1);
}
let newNames = names.map(capitalizeItUp);
console.log(newNames);
Разберемся, как этот код работает. Нас интересует функция capitalizeItUp, переданная в виде аргумента методу map. Эта функция выполняется для каждого элемента, и стоит обратить внимание, что текущий элемент передается ей в качестве аргумента. Для ссылки на аргумент текущего элемента вы можете использовать любое имя на ваш выбор. Мы ссылаемся на этот аргумент с помощью банального item:
function capitalizeItUp(item) {
let firstLetter = item.charAt(0). toUpperCase();
return firstLetter + item.slice(1);
}
Внутри этой функции мы можем написать любой код для нужного изменения текущего элемента массива. Единственное, что остается сделать, — это вернуть значение элемента нового массива:
function capitalizeItUp(item) {
let firstLetter = item.charAt(0). toUpperCase();
return firstLetter + item.slice(1);
}
Вот и все. После выполнения этого кода map возвращает новый массив, в котором все элементы имеют заглавные буквы и расположены на соответствующих местах. Исходный массив остается неизмененным, имейте это в виду.
Функции обратных вызовов
Наша функция capitalizeItUp также известна как функция обратного вызова. Такие функции подразумевают два действия:
• передачу в качестве аргумента другой функции;
• вызов из другой функции.
Вы будете встречать ссылки на функции обратных вызовов постоянно. Например, когда мы вскоре начнем рассматривать методы filter и reduce. Если вы слышите о них впервые, то теперь будете иметь о них лучшее представление. Если же вы были знакомы с этими функциями ранее, тем лучше для вас.
Фильтрация элементов
При использовании массивов вы будете часто фильтровать (то есть удалять) элементы на основе заданного критерия (рис. 13.7).
Рис. 13.7. Уменьшение количества элементов
Например, у нас есть массив чисел:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
В данный момент в нем есть и четные, и нечетные числа. Предположим, что нам надо проигнорировать все нечетные и просмотреть только четные. Этого можно добиться с помощью метода filter, отфильтровав все нечетные числа, чтобы остались только нужные нам четные.
Используется метод filter аналогично методу map. Он получает один аргумент — функцию обратного вызова, а эта функция, в свою очередь, определяет, какие элементы массива отфильтровать. Это легче понять, взглянув на код:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
let evenNumbers = numbers.filter(function (item) {
return (item % 2 == 0);
});
console.log(evenNumbers);
Мы создаем новый массив evenNumbers, который будет содержать результат выполнения метода filter для массива numbers. Содержимым этого массива будут четные числа благодаря нашей функции обратного вызова, проверяющей каждый элемент, чтобы узнать, будет ли результат item % 2 (то есть будет ли остаток при делении на 2) равен 0. Если функция вернет true, то элемент будет отправлен в отфильтрованный массив. Если же вернется false, элемент будет проигнорирован.
Здесь стоит заметить, что наша функция обратного вызова не является явно именованной, как функция capitalizeItUp в предыдущем примере. Она является анонимной, но это не мешает ей выполнять свою работу. Вы будете часто встречать функции обратного вызова в анонимной форме, поэтому стоит знать такой способ их определения.
Получение одного значения из массива элементов
Последним мы рассмотрим метод reduce. Он достаточно странный. В случаях с методами map и filter мы начинали с массива, имеющего один набор значений, а заканчивали другим массивом с другим набором значений. Используя метод reduce, мы по-прежнему будем начинать с массива. А вот в конце будем получать всего одно значение (рис. 13.8).
Рис. 13.8. От множества к одному
Здесь для прояснения происходящего необходим пример.
Давайте еще раз используем массив чисел из предыдущего раздела:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
Мы хотим сложить все значения. В этом и есть смысл метода reduce, когда все значения массива сужаются в один элемент. Взгляните на этот код:
let total = numbers.reduce(function(total, current) {
return total + current;
}, 0);
console.log(total);
Мы вызываем reduce для массива чисел и передаем в него два аргумента:
• функцию обратного вызова;
• начальное значение.
Начинаем суммирование с начального значения 0, а функция отвечает за добавление каждого элемента массива. В отличие от предыдущих примеров, где функции получали только текущий элемент массива, функция для метода reduce задействуется в большей степени. В данном случае ей приходится иметь дело с двумя аргументами:
• первый содержит итоговое значение, полученное в результате всех произведенных на этот момент действий;
• второй — это текущий элемент массива.
Используя эти два аргумента, вы можете легко создавать различные сценарии, задействующие отслеживание чего-либо. В нашем примере, поскольку нам просто нужна сумма всех элементов массива, мы складываем total со значением current. Итоговым результатом будет 31.
Подробнее об аргументах функций обратных вызовов
Для методов map и filter в наших функциях обратных вызовов мы определяли только один аргумент, представляющий текущий элемент массива. Для метода reduce мы определяли два аргумента, представлявших итоговое значение и текущий элемент. Помимо этого, функции обратных вызовов имеют два опциональных аргумента, которые вы также можете определить:
• индекс текущего элемента массива;
• массив, для которого вызывается map, filter или reduce.
Для методов map и filter эти аргументы стали бы вторым и третьим. Для reduce они бы оказались третьим и четвертым. Вы можете никогда не столкнуться с необходимостью определять эти опциональные аргументы, но если они все же вам понадобятся, знайте, где их искать.
Мы почти закончили. Давайте взглянем на пример, где показана работа метода reduce с нечисленными значениями:
let words = ["Where", "do", "you", "want", "to", "go", "today?"];
let phrase = words.reduce(function (total, current, index) {
if (index == 0) {
return current;
} else {
return total + " " + current;
}
}, "");
console.log(phrase);
Здесь совмещается текстовое содержимое массива words (слов), чтобы создать значение, которое будет выглядеть как Where do you want to go today? (Куда ты хочешь пойти сегодня?) Обратите внимание, что происходит в функции обратного вызова. Помимо совмещения каждого элемента в одну фразу мы определяем опциональный третий аргумент, представляющий индекс нашего текущего элемента. Мы используем этот индекс для отдельного случая с первым словом, чтобы определить, нужен перед ним пробел или нет.
Экскурс в функциональное программирование
Как показали последние несколько разделов, методы map, filter и reduce существенно упрощают работу с массивами. Но они проявляют себя и еще в одной огромной области, которая известна как функциональное программирование. Функциональное программирование — это способ написания кода, где вы используете функции, которые:
• могут работать внутри других функций;
• избегают совместного использования и изменения состояния;
• возвращают один и тот же вывод для одного и того же ввода.
Есть и другие мелочи, которые можно было бы здесь перечислить, но для начала хватит и этого. Вы уже видели работу принципов функционального программирования в функциях обратных вызовов. Эти функции идеально подходят под три перечисленных критерия, поскольку могут быть добавлены в любую ситуацию или исключены из нее до тех пор, пока аргументы будут работать. Они не изменяют никакое состояние и полноценно работают внутри методов map, filter и reduce. Функциональное программирование — это занятная тема, требующая гораздо более тщательного рассмотрения. Поэтому пока оставим все как есть, а подробным изучением этой темы займемся позднее.
КОРОТКО О ГЛАВНОМ
Пожалуй, это все, что следует знать про массивы, так как именно для этого вы их и будете использовать чаще всего. По крайней мере, теперь вы точно сможете создать с их помощью список продуктов.
Дополнительные ресурсы и примеры:
• Перемешивание массива: http://bit.ly/kirupaArrayShuffle
• Выбор произвольного элемента массива: http://bit.ly/kirupaRandomItemArray
• Удаление повторов из массива: http://bit.ly/kirupaRemoveDuplicates
• Хеш-таблицы против массивов: href="http://bit.ly/kirupaHvA