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

Определение

Функция со ссылкой из внешней среды создает замыкание.

  • JS-функции могут получить доступ к своей лексической области (внешней области)
  • Функции могут запоминать свою лексическую область видимости независимо от того, где они выполняются.

Пример и настройка

function outer () {
  var a = 'hello';
  function inner() {
    console.log(a);
  }
  return inner;
}
outer()(); // prints "hello"

Нет необходимости определять переменные над определением функции.

function outer() {
  function inner() {
    console.log(a);
  }
  var a = 'hello'; // define variable after function definition
  return inner;
}
outer()(); // prints 'hello'

Вместо использования var мы можем использовать let или const.

function outer() {
  function inner() {
    console.log(a);
  }
  const a = 'hello';
  return inner;
}
outer()(); // prints 'hello'

Мы можем передать параметр родительскому методу и создать замыкание.

function outer(b) {
  function inner() {
    console.log(b);
  }
  return inner;
}
outer('I am param')(); // prints "I am param"

Закрытие может быть сформировано с помощью нескольких иерархий методов.

function evenOuter() {
  var val = 'From even outer';
  function outer() {
    function inner() {
      console.log(val);
    }
    return inner;
  }
  return outer;
}
evenOuter()()();

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

var val = 'Global scope';
function outer() {
    function inner() {
      console.log(val);
    }
    var val = 'Outer scope';
    return inner;
  }
outer()(); // prints 'Outer scope'

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

  • Каррирование функций
  • Оптимизированная функция высшего порядка
  • Запоминание
  • Сокрытие и инкапсуляция данных

Каррирование функций

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

function multiply(a) {
  return function(b) {
    return a * b;
  }
}
// Currying the multiply function
const double = multiply(2);
const triple = multiply(3);
console.log(double(5)); // Output: 10
console.log(triple(5)); // Output: 15

Функция высшего порядка

Функция высшего порядка принимает в качестве параметра другую функцию.

function repeat(fn, n) {
  return function(x) {
    for (let i = 0; i < n; i++) {
      fn(x);
    }
  }
}
function logWithPrefix(prefix) {
  return function(message) {
    console.log(`${prefix}: ${message}`);
  }
}
const repeatLog = repeat(logWithPrefix('INFO'), 3);
repeatLog('Hello'); // Output: INFO: Hello (three times)

Запоминание

Аналогично примеру каррирования функций.

Сокрытие данных и инкапсуляция

Рассмотрим следующий код,

let count = 0;
function increment() {
  count += 1
  console.log(count);
}
increment();
count = 2;

Здесь основной поток заключается в том, что мы можем увеличить count напрямую, не используя метод increment.

Чтобы всегда использовать increment для увеличения count, мы можем использовать замыкание.

function counter() {
  let count = 0;
  function increment() {
    count += 1
    console.log(count);
  }
  return increment;
}
const myIncrement = counter();
myIncrement(); // print 1
myIncrement(); // print 2

Интересный факт: каждый раз, когда мы вызываем метод counter, он всегда создает новую сессию.

function counter() {
  let count = 0;
  function increment() {
    count += 1
    console.log(count);
  }
  return increment;
}
const myIncrement1 = counter();
myIncrement1(); // prints 1
myIncrement1(); // prints 2
const myIncrement2 = counter();
myIncrement2(); // prints 1
myIncrement2(); // prints 2

Если нам нужна другая функция, decrement, то фрагмент кода не масштабируется. В этом случае мы можем использовать Function Constructor.

function Counter() {
  let count = 0;
  this.increment = function() {
    count += 1
    console.log(count);
  }
  this.decrement = function () {
    count -= 1;
    console.log(count)
  }
}
const myCounter = new Counter();
myCounter.increment(); // print 1
myCounter.increment(); // print 2
myCounter.decrement(); // print 1

Недостатки

Если не обращаться должным образом,

  • Может вызвать перерасход памяти
  • Утечки памяти