Великий Путь Вебмастера от идеи до интернет бизнеса
Блог Романа Чуешова
Начни зарабатывать в интернете на создании сайтов и блогов

Замыкания в javascript – поймете сейчас и умело будете применять потом

Доброго времени суток, гики веб-разработки. Сегодня мы углубим ваши знания языка и разберем замыкания в 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();

Если вам понравилась статья, то подписывайтесь на обновления блога и обязательно рассказывайте о нем единомышленникам. Пока-пока!

С уважением, Роман Чуешов

Прочитано: 833 раз
Этот блог уже читают
читай и ты!
Оставить коментарий
:p :-p 8) 8-) :lol: =( :( :-( :8 ;) ;-) :(( :o:
  • Александр

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

    01.11.2018 в 17:34