В этом посте будет просто объяснено, как работают Javascript Closures. Мы рассмотрим эти темы и часто задаваемые вопросы:
- Что такое закрытие Javascript
- В чем причина названия «Закрытие»
- На самом деле просмотр замыканий в отладчике
- как рассуждать о замыканиях при кодировании
- наиболее распространенные подводные камни его использования
Простой пример (ошибка включена)
Самый простой способ понять замыкания — это понять, какую проблему они пытаются решить. Давайте рассмотрим простой пример кода со счетчиком, который увеличивается в 3 раза внутри цикла.
Но внутри цикла что-то асинхронное выполняется со счетчиком. Возможно, был сделан серверный вызов, в этом случае давайте просто вызовем setTimeout
который будет откладывать его выполнение до истечения времени ожидания:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
// 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 приведенного выше фрагмента кода.
Когда наступит первый тайм-аут, замыкание отобразится на панели «Переменные области действия» отладчика:
Как мы видим, замыкание — это просто простая структура данных со ссылками на переменные, которые функция должна «запомнить», в данном случае это переменная i.
Но тогда, где ловушка?
Мы можем ожидать, что журнал выполнения покажет:
1
2
3
|
counter value is 0 counter value is 1 counter value is 2 |
Но настоящий журнал выполнения на самом деле:
1
2
3
|
counter value is 3 counter value is 3 counter value is 3 |
Это не ошибка, это способ закрытия. Функция регистрации — это замыкание (или закрытие, так как этот термин используется в обоих случаях), содержащее ссылку на переменную i.
Это ссылка, а не копия, поэтому происходит следующее:
- цикл заканчивается, и значение переменной i равно 3
- только позже истечет первый тайм-аут, и функция регистрации запишет значение 3
- истекает второй тайм-аут, и функция регистрации все еще регистрирует 3 и т. д.
Как получить другое значение счетчика для асинхронной операции?
Это может быть сделано, например, путем создания отдельной функции для запуска асинхронной операции. Следующий фрагмент даст ожидаемый результат:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
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 прозрачно запоминают любые переменные, необходимые для будущего выполнения, удобным способом.
Но остерегайтесь ловушки: замыкания хранят ссылки, а не копии (даже примитивных значений), поэтому убедитесь, что это действительно предполагаемая логика.
Ссылка: | Действительно понимание Javascript Closures от нашего партнера JCG Алексея Новика в блоге The JHades Blog . |