«Вы можете мне объяснить, что такое закрытие?»

Если вы работаете с полным стеком, веб-интерфейсом, серверной частью или делаете что-нибудь с JavaScript, вы, вероятно, слышали о термине «закрытие». Если вы проходите собеседование на должность инженера-программиста, вы можете столкнуться с вопросом, в котором вас попросят объяснить, что такое закрытие.

На поставленные выше вопросы легко ответить - просто запомните определение замыкания и приведите пару примеров.

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

Прежде чем мы углубимся в то, насколько надежно замыкание в JavaScript, давайте разберемся, что такое замыкание.

Замыкание - это функция, которая имеет доступ к своей «внешней» области видимости, которую она определяет. Следовательно, он может получить доступ к значению во внешней области, даже если включающая функция завершена.

Вот пример:

function takeOne() {
  let i = 0;
  return function incrementFunction() {
    return i++;
  }
}

Приведенный выше код представляет функцию, возвращающую другую функцию. Однако после того, как вы вызываете takeOne и получаете incrementFunction, incrementFunction запоминает локальную переменную takeOne, даже если takeOne уже завершился.

Преимущество закрытия

Первое преимущество замыканий - сохранение локальных переменных в области видимости. Поскольку функции JavaScript являются первоклассными гражданами, разработчики часто сталкиваются с конфликтом имен, что приводит к неожиданным результатам. Использование замыкания может помочь сохранить пространство имен в этой области как частную переменную. Вы часто видели это в прошлом кода jQuery, где определялся метод щелчка.

$(function() {
  var selections = []
  $(".something").click(function() { // this closure has access to the outer variable selections
    selections.push("something") // this are able to get the outer function selections
  })
})

Хотя это действительно один из вариантов использования закрытия, это может заставить вас задуматься: «Действительно ли это цель закрытия?» Вы все еще можете сомневаться в заявлении о том, каков общий вариант использования закрытия.

Второе преимущество, которое является скорее общим случаем использования, состоит в том, что он полезен в асинхронной среде.

Представьте, что вам нужно перебрать значение в массиве:

for(var i = 0 ; i< 3; i++) {
  setTimeout(() => console.log(i), 3000)
}

Что будет на выходе этой программы?

Он печатает 3 три раза. Поскольку setTimeout является асинхронным, к моменту завершения цикла внешняя область i также изменится на 3, и последующий вызов setTimeout во время цикла запускает обратный вызов и каждый раз печатает 3.

Как бы вы решили эту проблему?

Есть много способов, в том числе использование синтаксиса ES6 let вместо var для определения области действия на уровне блока и решения проблемы. Однако, если они хотят, чтобы вы решили эту проблему без использования какой-либо функции ES6, закрытие - ваш ответ.

function printSomething(i) {
  setTimeout(() => console.log(i), 3000)
}

for(var i = 0; i<3; i++) {
  printSomething(i)
}

Просто создавая другую внешнюю функцию вне setTimeout, вы определяете замыкание. Значение i сохраняется даже после завершения printSomething. Затем обратный вызов выводит на консоль 0 1 2.

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

Еще один пример

Давайте представим еще один пример, в котором вам нужно создать функцию, которая должна вызывать сторонний API, агрегировать результат и возвращать его вызывающей стороне.

function getAPI(cb) {
    setTimeout(() => cb("a"), 3000)
}

function getAPIB(cb) {
    setTimeout(() => cb("b"), 2000)
}

function getAPIC(cb) {
    setTimeout(() => cb("c"), 1000)
}

function aggregateValue() {
  var aggregateData = []
  
  // your implementation here
}

Прежде чем продолжить чтение решения, сделайте паузу на секунду и подумайте, как вы можете решить эту проблему без обещаний или async / await.

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

function aggregateValue(cb) {
  var aggregateData = []
  var numberAPICalledSoFar = 0
  
  function callback(value) {
    aggregateData = [...aggregateData, value]
    if(numberAPICalledSoFar < 2) {
      numberAPICalledSoFar++;
    }else {
      cb(aggregateData)
    }
  }
  getAPI(callback)
  getAPIB(callback)
  getAPIC(callback)
}

Поскольку getAPI, getAPIB, getAPIC все используют функцию обратного вызова, вы можете создать функцию обратного вызова, которая увеличивает количество счетчиков API, вызванных на данный момент. Как только количество вызываемых API превысит 2, вызовите возвращаемое значение обратного вызова.

Приведенный выше код снова использует возможности замыканий для сохранения локальной переменной включающей функции при ее запуске. Когда getAPI завершает свой вызов и вызывает функцию обратного вызова, функция обратного вызова обращается к внешней области aggregateValue, чтобы увеличить количество счетчиков, которые API завершает выполнение. Затем aggregateData возвращается с обратным вызовом из внешнего aggregateValue функции, которой требуются все агрегированные данные из всех сторонних API.

Запустите эту функцию:

aggregateValue((ans) => ans.foreach(console.log))

Забрать

  • Хотя замыкания изучаются в первом классе JavaScript, они являются более «продвинутой» функцией javaScript.
  • Проблема, которая сложна с ClosureClosure, заключается не в его концепции, а в его преимуществах и в том, почему вам нужно понимать его вариант использования.
  • Общие варианты использования замыканий заключаются в решении вычислений в асинхронной среде.

Весь исходный код в этом руководстве находится здесь.

Спасибо за прочтение! Если вам понравился этот пост, не стесняйтесь подписаться на мою рассылку, чтобы получать еженедельные эссе о карьере в технологиях, интересные ссылки и контент!

Вы можете подписаться на меня и подписаться на меня на Medium, чтобы увидеть больше подобных сообщений.