Нет недостатка в блогах, документации и переполнении стека вопросов о замыканиях в 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 осталась защищенной.

В каких еще ситуациях могут быть полезны замыкания? Как насчет того, где их следует избегать?

Видите ошибку? Дай мне знать!