Что такое закрытия? Зачем они нам нужны? Я собираюсь ответить на эти вопросы здесь.

В последних двух статьях мы говорили о reduce и о том, что reduce — это самый неправильно понимаемый метод массива в JavaScript. Сегодня я объясню закрытие. В моей компании ходит шутка, что 90% проблем я решил с помощью замыкания, без лишних слов,
пришло время закрытия.

Что такое закрытие?

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

В этом примере у нас есть функция с именем externalFunction, которая получает параметр с именем name. Эта функция также устанавливает внутреннюю переменную myName, которая инициализируется как «Джефф» (я обещаю, что я не нарцисс). Наконец, мы возвращаем другую функцию, innerFunction. Эта возвращенная функция собирается что-то напечатать на экране. Вроде бы ничего особенного, но давайте посмотрим в деталях, что происходит.

Когда мы запускаем функции JavaScript и эта функция завершается, все локальные переменные, созданные этой функцией, исчезают, ПУФ! В этом примере имя параметра будет рассматриваться как локальная переменная, как и переменная myName. Мы возвращаем innerFunction, и externalFunction завершает свое выполнение, поэтому переменные, созданные externalFunction, исчезают, или…

Возвращая innerFunction, мы косвенно переносим значения name и myName в эту функцию, подобно тому, как у innerFunction был небольшой мешок, куда он добавлял все эти переменные из внешней области видимости. В тот момент, когда innerFunction нуждается в одном из этих значений, таких как myName или name, он просто заглядывает в свою сумку и использует их.

В этом примере мы можем сделать что-то вроде следующего:

const welcomeDanny = externalFunction('Дэнни');
welcomeDanny(); // Приятно познакомиться, Дэнни. Меня зовут Джефф

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

Сколько уровней?

После использования примера с двумя уровнями возникает вопрос, насколько глубокими будут замыкания? Напишем пример:

Здесь вы заметили, что в нашем примере Mario Bros мы углубились на 4 уровня, и у нас все еще есть доступ к самой внешней области действия с первого уровня. Мы также написали более сжатую версию первых 9 строк в одну. Так сколько уровней? Столько, сколько вы хотите или еще лучше, столько, сколько вам нужно.

Есть ли преимущество в использовании замыканий?

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

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

Заметил, что функция fakeAPI — это та, которую мы обсуждали ранее, и withFakeAPI делает то же самое, единственная разница между ними заключается в том, что одна использует замыкания, а другая — нет. Предположим, мы собираемся вызвать 4 разные конечные точки. Для простоты назовем эти конечные точки:
/ep-1, /ep-2, /ep-3, /ep-4 (действительно оригинально, да? 😝)

Если бы мы использовали функцию fakeAPI, мы бы сделали что-то вроде этого

поддельныйAPI('/ep-1', 23);
поддельный API(‘/ep-1’, 24);
поддельный API(‘/ep-2’, 345);

Это не плохо, но не идеально. Возникает пара опасений, что, если одна из конечных точек изменится? Да, у нас потенциально может быть постоянная переменная для конечной точки, которая может решить эту проблему.

константа EP_1 = ‘/ep-1’;
константа EP_2 = ‘/ep-2

поддельный API(EP_1, 23);
поддельный API(EP_1, 24);
поддельный API(EP_2, 345);

Это работает, но также не идеально, мы повторяем одни и те же переменные повсюду. Попробуем решить эту проблему с помощью замыканий. С замыканиями мы потенциально могли бы создавать новые функции, которые будут иметь URL-адрес и возвращать новую функцию, которая ожидает идентификатор. Например:

export const callEP1 = withFakeAPI('/ep-1');
export const callEP2 = withFakeAPI('/ep-2');
export const callEP3 ​​= withFakeAPI('/ep-3');
export const callEP4 = withFakeAPI('/ep-4');

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

callEP1(23);
callEP1(24);
callEP2(345);

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

Конфиденциальность по умолчанию

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

Здесь у нас есть простой пример с функцией пользователя (простая фабричная функция). Эта функция получает два параметра name и age и возвращает новый объект со следующими функциями: getName, getAge, setName, setAge. Все эти функции имеют доступ к имени и возрасту благодаря замыканиям. Но вне этих замыканий никто не может изменить имя и возраст напрямую, только через setAge или setName. Благодаря замыканиям мы достигаем конфиденциальности по умолчанию без использования каких-либо частных ключевых слов, как в других языках или классах в JavaScript.

const danny = User('Danny', 11);
danny.getAge(); // 11
danny.getName(); // Дэнни
danny.setAge(12);
danny.age // undefined
danny.getAge(); // 12

Решение Curry для обработчика React

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

Обратите внимание, что на каждой итерации мы создаем стрелочную функцию, которая вызывает функцию fakeAPI с нужными нам значениями, в данном случае с нашим fakeURL и fakeID. Это довольно простая реализация, но не чистая. Давайте посмотрим на другой пример с использованием замыканий:

В этом примере мы можем увидеть силу замыканий и то, как сделать код более разборчивым. Наша функция withFakeAPI возвращает новую функцию. Мы назначаем эту функцию переменной callFakeEndpoint. callFakeEndpoint будет иметь нашу «/my-fake-endpoint», обратите внимание, как мы захватываем это значение, и это значение будет одинаковым для каждой итерации в people.map. Поскольку callFakeEndpoint возвращает другую функцию, которую событие onClick может правильно обработать, мы можем вызывать callFakeEndpoint с идентификатором текущего человека непосредственно на каждой итерации. По сравнению с предыдущим примером нам не нужно объявлять новую стрелочную функцию на каждой итерации.

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

Заключение

Используя замыкания, ваш код становится более удобным для сопровождения, читабельным, и это просто весело. Лучший способ стать лучше в них — это практиковаться. Просто для удовольствия (пожалуйста, не делайте этого в своей работе) попробуйте переписать функции, которые принимают более двух параметров, и измените их, чтобы использовать замыкания, по одному параметру за раз (каррирование), чтобы увидеть, какие преимущества или, возможно, недостатки вы видите, используя их. Перепишите класс, используя замыкания, особенно если в этом классе используются ключевые слова private и public. Все становится веселее, когда вы начинаете комбинировать замыкания и сокращения вместе для конвейерной обработки и/или составления функций. Возможно, мы сможем объединить их позже в другой статье (😉). Дайте мне знать ваше мнение о закрытии в комментариях.

Не забудьте подписаться или подписаться, чтобы получать мой контент еженедельно.
Также вы можете следить за мной в твиттере: https://twitter.com/jcar787