Недавно я столкнулся с интересным вопросом кода, касающимся замыканий в JavaScript, ответ на который я едва запомнил и определенно никогда не использовал. Это выглядело так:

multiply3Numbers(2)(3)(4)
// write this function so that it returns the product of the 3 arguments given

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

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

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

function multiply3Numbers(a) {
  var product = a
  
  function multiplyBySecond(b) {
    product *= b
    return multiplyByThird
  }
  function multiplyByThird(c) {
    product *= c
    return product
  }
  return multiplyBySecond
}  

При этом вы можете видеть каждый шаг функции, а также ее замыкания. Когда вы впервые вызываете multiply3Numbers, вы даете ему начальный аргумент a. Затем этому аргументу присваивается переменная product. Возврат этой исходной функции дает вам multiplyBySecond, который ожидает второй аргумент. Если вы посмотрите на консоль, то увидите, что:

multiply3Numbers(2)
// returns the function multiplyBySecond

Поскольку multiply3Numbers выводит другую функцию, мы можем затем вызвать эту функцию, передав ей аргумент:

multiply3Numbers(2)(3)
// returns the function multiplyByThird

Теперь, как мы видели при определении функции multiplyBySecond выше, она знает переменную product из своей родительской функции по области видимости (которая инициализируется аргументом, переданным multiply3Numbers), и умножает это произведение на аргумент, переданный multiplyBySecond. Итак, прямо сейчас product равно 2 * 3 = 6. Однако multiplyBySecond возвращает multiplyByThird. Когда мы вызываем следующую функцию в цепочке, multiplyByThird, мы еще раз умножаем product на заданный аргумент.

multiply3Numbers(2)(3)(4)
// returns 24

multiplyByThird теперь возвращает product, что равно 24. Эта цепочка работает только потому, что две внутренние функции были определены в этой основной функции, и поэтому сохраняется переменная product. Тогда нет необходимости передавать product этим функциям в качестве аргумента, и решение будет достигнуто.