Classic closure example (ES5)

Вопрос, что такое замыкание в JavaScript многих вводит в ступор. Недавно с одним из своих учеников, я поймал один из классических примеров замыкания, который довольно прост и поможет понять вам, как умело можно использовать его магию. 

Давайте попробуем:

Задача:

 У нас есть коллекция из четырех DOM элементов, и массив имен такой же длины. Нужно при клике на DOM элемент выводить имя из массива. Имя массива соответствует индексу DOM элемента.

------

HTML разметка: 

<ul class="list">
 <li class="list__item"> First </li>
 <li class="list__item"> Second </li>
 <li class="list__item"> Third </li>
 <li class="list__item"> Fourth </li>
</ul>
// Массив элементов

var names = ['Andrew', 'Tatyana', 'Dasha', 'Victoria'];

// Коллекция DOM элементов

var items = document.querySelectorAll('.list__item');

// Проходимся по ним в цикле

for (var i = 0; i < items.length; i += 1) {
  var item = items[i];
  
  item.addEventListener('click', function () {
    console.log(names[i]);
  })
}

Пробуем нажимать на разные блоки, и что мы видим? При нажатие на любой из элементов, всегда выводится последний элемент массива => `Victoria`.

Почему? 

Вся проблема в том, что после отработки цикла, значения переменной-счетчика на последней итерации будет равен четырем.

Учитывая, что это var переменная, у нее отсутствует блочная видимость, и она видна за пределами блока for . Она не замыкается внутри цикла .

В момент срабатывания функции-обработчика, она обращается к текущему значению переменной. А оно равно четырем, потому что цикл уже отработал. Как нам решить эту проблему, оставаясь с тем же способом итерации через for

1 вариант - Замыкание через IIFE

Здесь нам поможет IIFE

IIFE (Immediately Invoked Function Expression) это JavaScript функция, которая выполняется сразу же после того, как она была определена.

Обновленный пример с замыканием.

for (var i = 0; i < items.length; i += 1) {
    var item = items[i];

    (function () {
      var k = i;
  
      item.addEventListener('click', function () {
        console.log(k);
      })
  }())
}

Данные теперь выводятся корректно! Что же здесь произошло? 

Мы замкнули нашу переменную счетчик с помощью анонимной IIFE функции. Функция объявляется, и сразу вызывается. Внутри нее действует свой контекст => this

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

JSFiddle

2 вариант - Заменить var на let

С приходом ES2015, у нас ушло много проблем с областью видимостью, и замыканиями. Вариант с let очень простой.

// Проходимся по ним в цикле

for (let i = 0; i < items.length; i += 1) {
  let item = items[i];
  
  item.addEventListener('click', function () {
    console.log(names[i]);
  })
}

Все отрабатывает так, как положено. Счетчик переменная не видна за блоком for и замыкается внутри тела цикла. Поэтому переменная как бы объявляется каждый раз заново.


Надеюсь, вам было интересно! Удачного дня.

  • Slider

You may also like

  • Slider
  • Slider
2 comments

John Doe

Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non | numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Reply