Недавно я прочитал несколько статей о замыканиях, и когда я действительно разобрался в них позже, я обнаружил, что большинство статей немного сложнее.
На самом деле, сначала я думал, что замыкание - это очень высокая и непостижимая точка знаний, но это не так.Если вы просто посмотрите на описание, это оченьофициальное определениетак же какОгромный индикатор закрытия статьиЕсли вы это сделаете, вас действительно уговорят сначала уволить😰. Далее я постараюсь максимально просто описать этот пункт знаний на примерах, ОК, приступим!
Прежде чем начать, если выобъем,контекст выполненияа такжеЛексическое окружениеЕсли концепция не ясна, рекомендуется сначала прочитать:Подробно объясните область действия JavaScript и цепочку областей видимости 🔗,Тщательно понимать объем, контекст выполнения, лексическое окружение 🔎, иначе некоторые описания в этой статье могут быть вам не очень дружелюбны 🤔
определение
Во-первых, выбросим определение замыкания в MDN:
функция и окружающее ее состояние (лексическое окружение, лексическое окружение) объединены вместе (или функции окружены ссылками), такая комбинацияЗакрытие(closure). То есть замыкания позволяют вам получить доступ к области действия внешней функции изнутри внутренней функции. В JavaScript всякий раз, когда создается функция, замыкание создается одновременно с созданием функции.
Хорошо, не имеет значения, если вы не понимаете вышеизложенного, давайте сначала рассмотрим пример:
Пример 1: вернуть функцию в функции
function makeFunc() {
var name = "Mozilla"
function displayName() {
alert(name)
}
return displayName
}
var myFunc = makeFunc()
myFunc() // Mozilla
var myFunc = makeFunc()
серединаmakeFunc()
Создается соответствующий контекст выполнения функции, в котором объявляется локальная переменнаяname
и объявляет функциюdisplayname
, и, наконец, вернутьсяdisplayname
Указатель (или ссылка) на функцию, т.е.function displayName(){alert(name);}
вернулся вmyFunc
Переменная
var myFunc = makeFunc()
После завершения выполнения соответствующий контекст выполнения функции извлекается из стека, в обычных условиях его переменные также уничтожаются, ноmyFunc()
называетсяmyFunc
, то есть выполняетсяfunction displayName(){alert(name);}
,здесьname
цитируетсяmakeFunc
В переменнойname
, поэтому его переменные не будут уничтожены вместе с ним, что эквивалентно инкапсуляции приватной переменной.
Это закрытие.
Хорошо, помимо конкретных примеров, давайте опишем понятие замыканий человеческими словами:
Замыкание — это функция, которая ссылается на переменную внутри другой функции.Поскольку на переменную ссылаются, когда выполнение другой функции завершается и соответствующий контекст выполнения выталкивается из стека, переменная не будет переработана, поэтому ее можно использовать для инкапсуляции частная переменная. Это одновременно и преимущество, и недостаток, ненужные замыкания только увеличивают потребление памяти, потому что неиспользуемые переменные вовремя не собираются.
Описание Ян Цзинь:
Закрытая сумка естьфункциятак же каклексическое окружение, в котором функция была объявленакомбинированный. Лексическое окружение содержит все локальные переменные, которые находились в области видимости при создании замыкания. (также из MDN)
Хорошо, вернемся к приведенному выше примеру, функцияmakeFunc
и его лексическое окружение (включая переменныеname
и функцияdisplayName
) называется замыканием. в лексическом окруженииname
Переменные ссылаются и не будут уничтожены.
Расширение: Далее мы рассмотрим похожий, но немного другой код:
function makeAdder(x) {
return function(y) {
return x + y
}
}
var add5 = makeAdder(5)
var add10 = makeAdder(10)
console.log(add5(2)) // 7
console.log(add10(2)) // 12
add5 = null // 释放对闭包的引用
console.log(add5(1)) //Uncaught TypeError: add5 is not a function
В этом примере мы определяемmakeAdder(x)
функция, которая принимает один параметрx
и возвращает новую функцию. Возвращаемая функция принимает один параметрy
, и вернутьсяx+y
ценность .
По сути,makeAdder
является фабрикой функций — она создает функции, которые добавляют и суммируют указанное значение и его аргументы. В приведенном выше примере мы использовали фабрику функций для создания двух новых функций — одна суммирует свои аргументы с 5, а другая с 10.
надadd5
а также add10
Все замыкания. Они используют одно и то же определение функции, но сохраняют другое лексическое окружение (переменная x).. существует add5
в окружающей среде,x
это 5. пока вadd10
середина,x
это 10.
Пример 2: Немедленно выполнить функцию
var add = (function () {
var counter = 0;
return function () {
return counter += 1;
}
})();
add();
add();
add(); // 3
Приведенное выше немедленно выполняет функцию и, наконец, помещаетfunction () {return counter += 1;}
Вернуться кadd
переменная, выполнитьadd()
когда он выполняетсяfunction () {return counter += 1;}
но не заявляетcounter
Эта переменная, ранее объявленная в функции немедленного выполнения, когда выполнение функции немедленного выполнения будет завершено, соответствующий контекст выполнения функции будет извлечен из стека вызовов, а переменная в немcounter
будет уничтожен, но мы можем нормально выполнитьadd()
вывод кода3
, проиллюстрироватьcounter
Переменные не уничтожаются, что тоже является проявлением замыканий.
Пример 3: проблема с печатью по таймеру
Чтобы лучше описать каждую часть кода, он разделен на три абзаца, которые можно написать здесь:
for(var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i)
}, i * 1000)
}
Ожидается, что приведенный выше код будет выводиться отдельно каждую секунду.0, 1, 2, 3, 4
, но на самом деле вывод, в свою очередь,5, 5, 5, 5, 5
.
В приведенном выше кодеПеременнаяi
объявляется командой var и действует в глобальной области видимости, так что есть только одна переменная глобальноi
. В каждом раунде цикла переменнаяi
Значение перепишет значение предыдущего раунда.
Обратный вызов вышеуказанной функции таймера не будет выполняться до конца цикла. Конечным условием этого цикла являетсяi
больше никогда< 5
, когда условие впервые установленоi
Значение5
, поэтому вывод показывает окончательное значение i в конце цикла. Поэтому каждый раз выводите 5.
for(var i = 0; i < 5; i++) {
(function(j){
setTimeout(() => {
console.log(j)
}, j * 1000)
})(i)
}
Код изменен на приведенный выше, и он может работать так, как мы ожидаем. После этой модификации использование IIFE (функция немедленного выполнения) на каждой итерации будет генерировать новую область для каждой итерации, так что обратный вызов отложенной функции может заключать новую область внутри каждой итерации, каждая итерация Внутри будет переменная с правильное значение, к которому можно получить доступ.
Вся вышеприведенная функция немедленного выполнения является замыканием, а переменная j является частью замыкания.Когда выполнение функции немедленного выполнения заканчивается, на переменную j ссылается таймер.
for(let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i)
}, i * 1000)
}
Замена var на let с блочной областью действия ES6 также послужит нашей цели.
В приведенном выше кодеПеременнаяi
даlet
заявленный, текущийi
Действует в этом цикле, поэтому каждый циклi
На самом деле это новая переменная, поэтому окончательный вывод0, 1, 2, 3, 4
. Вы можете спросить, является ли переменная для каждого раунда циклаi
Все они повторно объявлены, так как же узнать значение предыдущего цикла, чтобы вычислить значение этого цикла? Это связано с тем, что движок JavaScript внутренне запомнит значение предыдущего цикла и инициализирует переменные этого цикла.i
рассчитывается на основе предыдущего цикла.
Расширение: Давайте посмотрим на аналогичный фрагмент кода:
var data = []
for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i)
}
}
data[0]()
data[1]()
data[2]()
// 3 3 3
// 使用闭包
var data = []
for (var i = 0; i < 3; i++) {
data[i] = (function (j) {
return function () {
console.log(j)
}
})(i)
}
data[0]()
data[1]()
data[2]()
// 0 1 2
// 使用let
var data = []
for (let i = 0; i < 3; i++) {
data[i] = function () {
console.log(i)
}
}
data[0]()
data[1]()
data[2]()
// 0 1 2
Хорошо, я считаю, что вы не должны видеть здесь никаких проблем. В дополнение к рассмотренным выше трем случаям наиболее классических замыканий давайте рассмотрим другие сценарии, где также используются замыкания.
Другие типы сценариев, в которых используются замыкания
1. Функция как параметр
var a = 'Rocky'
function foo(){
var a = 'foo'
function fo(){
console.log(a)
}
return fo
}
function f(p){
var a = 'f'
p()
}
f(foo()) // foo
Первый взглядf(foo())
внутриfoo()
, После его выполнения, которое является переменнойa
не был переработан, потому чтоfo()
цитируется, такfoo()
Его лексическое окружение — замыкание.
2. Использование функций обратного вызова — это использование замыканий
window.name = 'Rocky'
setTimeout(function timeHandler(){
console.log(window.name);
}, 100)
3. Дросселирование и защита от тряски
// 节流
function throttle(fn, timeout) {
let timer = null
return function (...arg) {
if(timer) return
timer = setTimeout(() => {
fn.apply(this, arg)
timer = null
}, timeout)
}
}
// 防抖
function debounce(fn, timeout){
let timer = null
return function(...arg){
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, arg)
}, timeout)
}
}
Роль замыканий
Хорошо, давайте, наконец, подытожим роль замыканий (отКлассические сценарии использования замыканий JS и обязательные вопросы с замыканиями)
- Защитите частные переменные функции от внешнего вмешательства. Формирует память стека, которая не уничтожается.
- Сохранить, сохранить значения в некоторых функциях. Замыкания могут сделать методы и свойства закрытыми
Некоторые студенты могут жаловаться: где код из трех частей?
На самом деле, кроме使用闭包的其他场景
а также示例一和三的扩展
, разве это не три абзаца😝 Эти две части просто эквивалентны расширению, я добавил их позже, это не влияет, если вы не читаете это, вы можете лучше понять это после прочтения😉
Наконец-то я внеклассный новичок, самоучка во фронтенде.Должно быть много неуместных описаний.Добро пожаловать на указание и совместное обсуждение 😇
Ссылаться на
Документация MDN: замыкания
Классические сценарии использования замыканий JS и обязательные вопросы с замыканиями
Глубокое понимание того, что замыкания JavaScript являются замыканиями
Предыдущая серия JS
Подробно объясните область действия JavaScript и цепочку областей видимости 🔗
Тщательно понимать объем, контекст выполнения, лексическое окружение 🔎
Выбранные 30+ кейсов и 10 вопросов помогут вам освоить это 👊
Краткое изложение Promise/async/await✨ [Выбранные выходные вопросы и рукописные вопросы]