Статьи

Действительно Понимание Закрытий Javascript

В этом посте будет просто объяснено, как работают Javascript Closures. Мы рассмотрим эти темы и часто задаваемые вопросы:

  • Что такое закрытие Javascript
  • В чем причина названия «Закрытие»
  • На самом деле просмотр замыканий в отладчике
  • Как рассуждать о замыканиях при кодировании
  • Наиболее распространенные подводные камни его использования

Простой пример (ошибка включена)

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

Но внутри цикла что-то асинхронное выполняется со счетчиком. Может случиться так, что был сделан серверный вызов, в этом случае давайте просто вызовем
setTimeoutэто, чтобы отложить его выполнение до истечения времени ожидания:

// define a function that increments a counter in a loop
function closureExample() {
 
    var i = 0;
 
    for (i = 0; i< 3 ;i++) {   
        setTimeout(function() {
            console.log('counter value is ' + i);
        }, 1000);
    }
 
}
// call the example function
closureExample();  

Некоторые вещи, которые нужно иметь в виду:

  • переменная i существует в области действия closureExampleфункции и не доступна извне
  • во время цикла по переменной console.logоператор не выполняется сразу
  • console/log будет выполняться асинхронно 3 раза и только после истечения 1 секунды
  • Это означает, что установлены 3 таймаута, а затем closureExampleвозвращается почти сразу

Что приводит нас к основному вопросу об этом коде:

Когда выполняется анонимная функция ведения журнала, как она может получить доступ к переменной «i»?

Вопрос возникает с учетом того, что:

  • переменная я не был передан в качестве аргумента
  • когда console.log statementвыполняется, closureExampleфункция давно закончилась.

Так что же такое закрытие?

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

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

Такая функция с «памятью» о среде, в которой она была создана, называется просто: Закрытие .

Почему имя Закрытие тогда?

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

Есть ли способ увидеть закрытие?

Самый простой способ — использовать отладчик Chrome Developer Tools и установить точку останова в строке 7 приведенного выше фрагмента кода.

Когда наступит первый тайм-аут, замыкание отобразится на панели «Переменные области действия» отладчика:

Просмотр закрытия Javascript

Как мы видим, замыкание — это просто простая структура данных со ссылками на переменные, которые функция должна «запомнить», в данном случае это переменная i.

Но тогда, где ловушка?

Мы можем ожидать, что журнал выполнения покажет:

counter value is 0
counter value is 1
counter value is 2

Но настоящий журнал выполнения на самом деле:

counter value is 3
counter value is 3
counter value is 3

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

Это ссылка, а не копия, поэтому происходит следующее:

  • цикл заканчивается, и значение переменной i равно 3
  • только позже истечет первый тайм-аут, и функция регистрации запишет значение 3
  • истекает второй тайм-аут, и функция регистрации все еще регистрирует 3 и т. д.

Как получить другое значение счетчика для асинхронной операции?

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

function asyncOperation(counter) { 
    setTimeout(function() {
        console.log('counter value is ' + counter);
    }, 1000);
}
 
function otherClosureExample() { 
    var i = 0;
 
    for (i = 0; i < 3 ;i++) {   
        asyncOperation(i);
    }
}
 
otherClosureExample();  

Это работает, потому что при вызове asyncOperationкопии создается значение счетчика, и регистрация «закрывает» это скопированное значение. Это означает, что каждый вызов функции регистрации будет видеть другую переменную со значениями 0, 1, 2.

Вывод

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

Они могут быть удобным способом уменьшить количество параметров, передаваемых в функцию.

Но главным образом тот факт, что закрытые переменные недоступны
извне функции, делает замыкания хорошим способом достижения «закрытых» переменных и инкапсуляции в Javascript.

В основном функция «просто работает», а функции Javascript прозрачно запоминают любые переменные, необходимые для будущего выполнения, удобным способом.

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