Нет недостатка в блогах, документации и переполнении стека вопросов о замыканиях в JavaScript. Вы должны прочитать и понять их. Но я подумал, что было бы полезно внести свой собственный код с тяжелым кодом, полу-непрофессиональную версию терминов.
Во-первых, давайте кратко поговорим о масштабе.
let balance = 0 function deposit(amount){ let newLargerBalance = balance + amount balance = newLargerBalance //This is a silly way to do this return balance } function withdraw(amount){ let newSmallerBalance = balance - amount balance = newSmallerBalance //This is a silly way to do this return balance }
Обе функции deposit
и withdraw
имеют доступ к переменной balance
, поскольку она объявлена «вне» соответствующих функций. Точно так же мы можем получить доступ к balance
глобально:
> console.log(`Your balance is $${balance}.`) Your balance is $0. > deposit(50) > console.log(`Your balance is $${balance}.`) Your balance is $50.
deposit
и withdraw
также имеют доступ к своей локальной области; В этом случае newSmallerBalance
доступен в withdraw
, а newLargerBalance
доступен в deposit
. Ни одна из этих переменных не доступна вне их соответствующих функций:
> console.log(newLargerBalance) Uncaught ReferenceError: newLargerBalance is not defined
Идем дальше. Если это не имеет смысла, найдите время, чтобы изучить, как область действия работает в JavaScript (примечание: ключевое слово var
добавляет еще один уровень сложности. Вы должны понимать его, но обычно избегайте его использования).
Давайте смоделируем банковский счет с помощью функции-конструктора. (Мы также могли бы сделать это с помощью класса или фабричной функции, подробнее о них здесь.)
function bankAccountConstructor(startingBalance) { this.balance = startingBalance this.deposit = amount => this.balance += amount this.withdraw = amount => this.balance -= amount }
Теперь мы можем создать банковский счет и получить доступ к балансу его свойств, депозиту и выводу средств. Давайте посмотрим на некоторые вещи, которые мы можем сделать.
//create new bank account with a starting balance of $500 > let myAccount = new bankAccountConstructor(500) > console.log(`Your balance is $${myAccount.balance}.`) Your balance is $500. //withdraw $50 > myAccount.withdraw(50) > console.log(`Your balance is $${myAccount.balance}.`) Your balance is $450. //Change our balance directly. Yay! But not for the developer in us > myAccount.balance = 1000000 > console.log(`Your balance is $${myAccount.balance}.`) Your balance is $1000000. // Umm... we can do this I guess > myAccount.balance = "a whole lot of money" > myAccount.deposit(33) > console.log(`Your balance is $${myAccount.balance}.`) Your balance is $a whole lot of money33. // yeah... > myAccount.withdraw(44) console.log(`Your balance is $${myAccount.balance}.`) Your balance is $NaN. //boo
Мне нравится предполагать лучшее из людей, но я не думаю, что прямой доступ для записи к остатку на банковском счете закончится хорошо. Как мы могли бы изменить это, не теряя желаемой функциональности? Ты угадал! «Классы!» Какие? Нет. Закрытие. Это прямо в заголовке. Давай, люди.
Что такое замыкание.
Давайте перепишем наш bankAccountConstructor
.
function bankAccountConstructor(startingBalance) { let balance = startingBalance // this.deposit = amount => balance += amount this.withdraw = amount => balance -= amount }
Мы удалили свойство this.balance
и вместо этого объявили локальную переменную, которая недоступна напрямую за пределами bankAccountConstructor
. Но теперь мы не можем проверить свой баланс без депозита или вывода средств, поэтому давайте добавим геттер-метод.
function bankAccountConstructor(startingBalance) { let balance = startingBalance // this.deposit = amount => balance += amount this.withdraw = amount => balance -= amount this.getBalance = () => balance }
Вот что происходит, когда мы пытаемся получить доступ к балансу сейчас:
> let myAccount = new bankAccountConstructor(500) > console.log(`Your balance is $${myAccount.balance}.`) Your balance is $undefined.
У нас нет прямого доступа к balance
, потому что мы находимся «вне» функции bankAccountConstructor
. Это не в рамках. ОДНАКО методы this.deposit
, this.withdraw
и this.getBalance
имеют доступ к balance
. Глядя на наше определение замыкания выше, эти методы являются «внутренней функцией[s]», а bankAccountConstructor
— «внешней функцией». Давайте докажем это.
//create new bank account with a starting balance of $500 > let myAccount = new bankAccountConstructor(500) //We now have to invoke the getBalance() method > console.log(`Your balance is $${myAccount.getBalance()}.`) Your balance is $500. > myAccount.withdraw(50) //withdraw $50 > console.log(`Your balance is $${myAccount.getBalance()}.`) Your balance is $450.
Уот! Теперь мы можем контролировать доступ к balance
с помощью методов, которые мы предоставляем bankAccountConstructor
.
Бонус:
> let myAccount = new bankAccountConstructor(500) > myAccount.balance = "Infinite money! Muahahaha!" > console.log(myAccount.balance) Infinite money! Muahahaha! > console.log(myAccount.getBalance()) 500
Нам удалось добавить новое свойство this.balance
в myAccount
, но переменная balance
осталась защищенной.
В каких еще ситуациях могут быть полезны замыкания? Как насчет того, где их следует избегать?
Видите ошибку? Дай мне знать!