Доброго времени суток, гики веб-разработки. Сегодня мы углубим ваши знания языка и разберем замыкания в JavaScript. Это очень важный, ключевой раздел при изучении JS, без которого по сути и «каши не сваришь».
Поэтому в текущей публикации мы пройдемся с вами по основным моментам замыкания для того, чтобы не быть чайниками. Я объясню, что это такое и с какими ошибками можно столкнуться. Также приведу несколько примеров для лучшего понимания материала. Как говорится: «Меньше слов, больше дела». Так что за дело!
Что подразумевает под собой замыкание
В JavaScript замыкание – это обычные вложенные функции, которые используют свободные (независимые) переменные, находящиеся в их окружении.
Такое окружение называется лексической областью видимости – Lexical scoping. Если более простыми словами, то это механизм, который позволяет вложенным функциям использовать переменные, объявленные вовне их тела, и «замыкать» последние на себе.
Рассмотрим пример. В коде создается функция с названием IntCounter (), в которой объявляется локальная переменная calls и вложенная функция. Последняя должна возвращать количество вызовов в основном коде.
1 2 3 4 5 6 7 8 9 10 | function IntCounter() {
var calls = 0;
return function() {
return ++calls;
}
}
var CountСalls = IntCounter ();
CountСalls(); //1
CountСalls(); //2
CountСalls(); //3 |
В этом случае вложенной функции доступна локальная переменная внешней. И в добавок к этому данная переменная продолжает существовать и быть доступной для вложенной, несмотря на завершение выполнения основной функции.
Именно поэтому в прикрепленном выше примере переменная calls продолжает свое существование и сохраняет последнее присвоенное значение.
Почему данный механизм возможен?
Вот тут будьте внимательны!!! Попытайтесь хорошенько разобрать и запомнить прочитанное. В будущем это поможет вам понимать более сложные вещи в JS.
Итак, ссылки на внешние переменные, объекты хранятся во внутреннем свойстве вложенной функции под названием [[Scope]]. Это скрытое свойство, которое присваивается функциям при их создании и ссылается на их Lexical scoping.
[[Scope]] привязывается к конкретной функции и таким образом создает связь между ней и ее местом рождения. Значение [[Scope]] сохраняется и поэтому в примерах выше была возможность получить последнее значение и увеличить его.
Практическое применение замыканий
Данная штука очень полезна на практике. Особенно часто к ней обращаются в веб-приложениях, в которых используется множество событий.
Так как замыкания позволяют простым способом связать некоторые действия, переменные, параметры и сохранять значения после отработки методов, то они значительно упрощают написание определенных задач во время их программной реализации.
Для примера можно привести тривиальную задачу, которая иногда встречается при программировании онлайн-библиотек, блогов со статьями или других сайтов, где можно читать какую-либо литературу. Ваш сервис предлагает пользователю прочтение каких-то книг.
После открытия определенного источника для юзера стандартный шрифт может оказаться слишком мелким или наоборот слишком крупным. Поэтому ему предлагается выбрать размер символов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | <!DOCTYPE html>
<html>
<head>
<title>Практический пример использования замыкания</title>
<meta charset="utf-8">
<style>
body {
font-family: Arial, sans-serif;
font-size: 14px;
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.2em;
}
</style>
</head>
<body>
<h2>Выберите размер шрифта</h2>
<br/>
<button id="size_12">12</button>
<button id="size_16">16</button>
<button id="size_20">20</button>
<br/><br/>
<p>Здесь написан какой-то текст исторического романа.</p>
<h1>Том первый. Глава вторая.</h1>
<p>Продолжение увлекательной истории...</p>
<script>
function ChangeSize(newSize) {
return function() {
document.body.style.fontSize = newSize + 'px';
};
}
var size12 = ChangeSize(12);
var size16 = ChangeSize(16);
var size20 = ChangeSize(20);
document.getElementById('size_12').onclick = size12;
document.getElementById('size_16').onclick = size16;
document.getElementById('size_20').onclick = size20;
</script>
</body>
</html> |
Наиболее распространенные ошибки
К частым ошибкам можно отнести замыкание в цикле. Для лучшего понимания представьте, что у вас имеется программа, в которую вы вносите изучаемые вами английские слова. Для проверки правильности перевода, подсказка выводится при наведении на конкретное слово.
Обычно новички пишут код следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <!DOCTYPE html>
<html>
<head>
<title>Помощник при изучении английских слов</title>
<meta charset="utf-8">
</head>
<body>
<p id="help">Наведите на слово для получения перевода.</p>
<p id="1">elections</p>
<p id="2">electricity</p>
<p id="3">electric</p>
<script>
function showTranslation (translation) {
document.getElementById('help').innerHTML = translation;
}
function DictionaryHelp() {
var helpText = [
{'id': '1', 'help': 'Выборы'},
{'id': '2', 'help': 'Электричество'},
{'id': '3', 'help': 'Электрический'}
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onmouseover = function() {
showTranslation (item.help);
}
}
}
DictionaryHelp();
</script>
</body>
</html> |
Однако при запуске программы и наведении на любое из слов, ответ всегда будет один и тот же – перевод слова «электрический».
Почему же так? На самом деле все функции, выполняющиеся как обработчики событий, реализуют все то же замыкание. Поэтому в коде было создано целых три замыкания (так как элементов в цикле всего три).
Однако все они ссылаются на одно и то же окружение, ведь ко времени выполнения события цикл уже был пройден и конечным обработанным элементом остался последний в массиве.
Для решения этой проблемы в новых версиях (начиная с ECMAScript 6) можно использовать ключевое let. В других ситуациях следует обратиться за помощью к function factory.
Для реализации такого подхода стоит написать дополнительную функцию, которая позволит создать три отдельные лексические области видимости.
Скрипт изменится следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | function showTranslation(translation) {
document.getElementById('help').innerHTML = translation;
}
function HelpCallback(help) {
return function() {
showTranslation(help);
};
}
function DictionaryHelp() {
var helpText = [
{'id': '1', 'help': 'Выборы'},
{'id': '2', 'help': 'Электричество'},
{'id': '3', 'help': 'Электрический'}
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onmouseover = HelpCallback(item.help);
}
}
DictionaryHelp(); |
Если вам понравилась статья, то подписывайтесь на обновления блога и обязательно рассказывайте о нем единомышленникам. Пока-пока!
С уважением, Роман Чуешов

















Роман, объясните пожалуйста мне что вызывает функцию showTranslation в последнем примере? Ведь после срабатывания обработчика событий в 18й строчке вызывается функция HelpCallback, которая только возвращает, но не вызывает функцию showTranslation