Тройное C: Currying, Closure и Callback в JavaScript

Источник: «Triple CCC: Currying, Closure, and Callback in JavaScript»
В этой статье вы узнаете о концепции Тройного C в JavaScript, включающей в себя Currying, Closures и Callbacks.

Оглавление

Если хотите построить карьеру на языке JavaScript, вам необходимо хорошо разбираться в его ключевых концепциях. Тройное C: Currying, Closure, Callback в JavaScript — это то, что необходимо понять, для успешного прохождения собеседований. Это поможет не только получить знания по данным темам, но и даст дополнительное преимущество на собеседовании. Ведь чаще всего интервьюеры задают распространённые вопросы из этих тем.

Функция как Объект первого класса

Прежде чем перейти к рассмотрению концепций Тройного C, давайте вкратце познакомимся с функциями в JavaScript и посмотрим, насколько они мощны. В языках программирования существует понятие Объект первого класса, которое относится к передаче, возврату и присвоению типа. Если тип способен выполнять эти действия, то он будет считаться Объектом первого класса.

В языке JavaScript функция может выполнять все задачи, и поэтому она Объектом первого класса. Рассмотрим приведённый ниже пример кода функций JavaScript:

let addToNumbers = function(a, b) {
return a + b;
}

let sum = addToNumbers(10,10);
console.log(sum);

// Выводит: 20

Здесь вы видите, что мы объявили функцию, а затем сохранили её в переменной. Такое поведение известно как "Объект первого класса". Эта концепция поможет нам понять следующие темы. Давайте начнём с Currying в следующем разделе.

Currying/Каррирование

Термин "Каррирование/карринг" — это парадигма программирования или паттерн. Точнее, это паттерн функционального программирования, и придумал его Хаскелл Карри. Каррирование используется, когда мы можем разбить несколько параметров функции по одному.

Это процесс преобразования функций с несколькими параметрами в функцию с одним параметром. Мы разберёмся в концепции каррирования, решив самый популярный вопрос на собеседовании. Сначала давайте посмотрим на вопрос.

function multiply(x, y, z){
return x * y * z
}

console.log(multiply(5, 5, 5))

// Выводит: 125

На собеседовании вы можете получить вопрос такого типа, и задача, которую вам нужно решить, — вызвать функцию multiply(5)(5)(5) вместо multiply(5, 5, 5). Как можно выполнить это действие? Ответ прост — это называется каррирование. Посмотрите приведённый ниже пример кода:

function multiply(x){
return function(y){
return function(z){
return x * y * z
}
}
}

console.log(multiply(5)(5)(5))

// Выводит: 125

В вопросе было три параметра в одной функции. Мы просто разбиваем эти параметры и преобразуем их в функцию с одним параметром.

Это возможно благодаря поведению функционального программирования, когда мы можем вызывать функцию внутри другой функции и создавать цепочку функций. Результат тот же, но теперь мы используем паттерн каррирования, чтобы получить этот результат.

Closure/Замыкание

Одной из самых важных концепций JavaScript являются замыкания. Проще говоря, замыкание — это акт закрывания. В JavaScript замыкание — это функция, которая ссылается на переменные во внешней области видимости из своей внутренней области видимости.

Прежде чем понять, что такое замыкание, нам нужно знать об области видимости переменной. У переменной есть три возможных области видимости, в зависимости от того, где она определена. Она может иметь локальную, глобальную или блочную область видимости (подробнее о лексическом окружении вы можете прочитать в статье Effective JavaScript - 10 JavaScript Concepts You Should Know).

Если переменная объявлена внутри функции, она будет относиться к локальной области видимости этой функции. Посмотрите фрагмент кода:

function demoFunc(){
let val = 10
return val + val
}

Здесь переменная val находится в локальной области видимости, и её время жизни будет существовать пока выполняется вызов функции. С другой стороны, когда переменная объявляется вне функции, она будет находиться в глобальной области видимости и будет представлена Объектом window браузера, если она объявлена с помощью var. Посмотрите приведённый ниже фрагмент кода:

let val = 10

function demoFunc(){
return val + val
}

На этот раз val находится в глобальной области видимости и будет существовать до тех пор, пока страница не будет удалена. Концепция замыкания используется для превращения глобальной переменной в локальную.

Давайте посмотрим на проблему, которую замыкание решает в следующей секции, и, сделав это, вы сможете понять, зачем нам нужно изучать концепцию замыкания в JavaScript.

function sayHello(){
let name = 'John'

function inner(){
console.log(`Hello ${name}`)
}

return inner
}

let abc = sayHello()
console.log(abc)

// Выводит:
// [Function: inner]
// Hello John

Здесь видно, что мы получаем ожидаемый результат. Но главный вопрос заключается в том, как мы это получаем? В чём причина этого? Можете ли вы догадаться, что на самом деле здесь происходит? Давайте разберём этот пример вместе.

Сначала мы объявили функцию sayHello() и сохранили строку в переменной name. Позже мы объявили ещё одну функцию и напечатали имя с сообщением Hello, а затем вернули функцию inner(). Наконец, мы сохранили возвращённую функцию в переменной abc.

Теперь, когда JavaScript выполнит код, он не найдёт переменную name, так как память функции sayHello() была уничтожена. Но как мы получаем ожидаемый результат?

Основная причина этого — концепция замыканий. Здесь действительно память функции sayHello() была уничтожена, но создаётся ссылка на эту функцию, и в результате при выполнении inner() она может обращаться к переменной из ссылки функции sayHello(). Чтобы выполнить это действие, не нужно писать ни строчки дополнительного кода. Потому что движок JavaScript достаточно умён, чтобы выполнить это действие. Всё, что вам нужно сделать, — это понять концепцию замыкания, которая создаётся по умолчанию каждый раз, когда в JavaScript создаётся функция.

Callback/Обратный вызов

Проще говоря, обратный вызов обозначает термин "вызвать позже". Это означает, что он ссылается на функцию, которая будет вызвана позже. Давайте представим себе реальный сценарий. Допустим, у нас есть массив, состоящий из названий фруктов. Массив сохранён в нашей базе данных. Нам нужно создать программу, которая будет добавлять новый фрукт и впоследствии выводить список фруктов с новым добавлением. Для выполнения этого действия мы написали следующий код:

let fruitNames = ['Apple','Banana','Orange']

function addNewFruit(name){
setTimeout(function(){
fruitNames.push(name)
},5000)
}

function printFruits(){
setTimeout(function(){
console.log(fruitNames)
},3000)
}

addNewFruit('Mango')
printFruits()

// Выводит:
// [ 'Apple', 'Banana', 'Orange' ]

Здесь мы создали новую функцию, добавляющую новые названия фруктов. Мы выполнили её, но новые названия фруктов не добавились. Основная причина этого — асинхронное поведение JavaScript.

В JavaScript, если задачи требуют много времени на выполнение, он автоматически сохраняет их в очереди и ищет те задачи, которые займут меньше времени. В нашем коде возникла такая ситуация. В результате мы не можем получить желаемый результат. Как же исправить эту ситуацию? Прежде чем переходить к решению, попробуйте сами подумать о том, как её решить.

Ответ на эту проблему прост. Всё, что нужно сделать, это использовать функцию обратного вызова, чтобы разрушить асинхронное поведение JavaScript, и он мог ждать нас, основываясь на определённой задаче. Посмотрите приведённый ниже пример кода:

let fruitNames = ['Apple','Banana','Orange']

function addNewFruit(name, cb){
setTimeout(function(){
fruitNames.push(name)
cb()
},5000)
}

function printFruits(){
setTimeout(function(){
console.log(fruitNames)
},3000)
}

addNewFruit('Mango',printFruits)

// Выводит:
// [ 'Apple', 'Banana', 'Orange', 'Mango' ]

В начале этой статьи мы уже узнали, что функция является Объектом первого класса в JavaScript. Здесь мы использовали только эту концепцию. Вы можете видеть, что мы передали функцию в качестве параметра другой функции.

Теперь, когда JavaScript будет выполнять код, он найдёт эту функцию обратного вызова и выполнит её только после того, как будут выполнены предыдущие функции. Эта концепция известна как обратный вызов, и вы можете видеть, что мы вызвали эту функцию после того, как предыдущая функция выполнила свои задачи. Именно по этой причине мы упомянули Обратный вызов как Вызов позже.

Заключение

В этой статье мы рассмотрели три наиболее важные концепции JavaScript. Они также известны как Тройное C (Currying, Closure, Callback). Если вы зашли так далеко, то, возможно, вы уже знаете об этих понятиях. Цель этой статьи — рассказать об этих сложных вещах самым простым способом.

Существует множество продвинутых концепций, которые были построены на основе обратного вызова. Вы можете часто слышать о понятии Callback hell, являющееся своеобразным кошмаром для JavaScript разработчиков.

Дополнительные материалы

Предыдущая Статья

Привязка Laravel маршрутов для конечных объектов

Следующая Статья

Улучшение UX форм с CSS свойством field-sizing