Что такое функция замыкания (closures) в JavaScript и как она работает

Замыкания (closures) в JavaScript
Что такое замыкания в JavaScript и как они работают? Подробное объяснение концепции замыканий с примерами кода. Область применения и возможные проблемы замыканий.

Замыкания (closures) — одна из самых мощных и в то же время сложных для понимания концепций в JavaScript. Многие начинающие разработчики испытывают трудности с пониманием замыканий. Давайте разберемся, что такое замыкания, как они работают и рассмотрим примеры их использования.

Что такое замыкание?

Замыкание — это комбинация функции и лексического окружения, в котором эта функция была объявлена. Проще говоря, замыкание позволяет функции иметь доступ к переменным из внешней (вмещающей) области видимости даже после того, как код внешней функции завершил свое выполнение.

Замыкания возникают в JavaScript благодаря лексической области видимости. Каждая функция в JS имеет доступ к области видимости, в которой она была создана. И даже если внешняя функция завершила работу, внутренняя функция по-прежнему имеет доступ к переменным и параметрам внешней функции.

Пример замыкания

Рассмотрим простой пример замыкания:


function outerFunc(x) {
  let y = 10;
  
  function innerFunc(z) {
    return x + y + z;
  }
  
  return innerFunc;
}

const closure = outerFunc(5);
console.log(closure(15)); // 30

Что здесь происходит:

  1. Мы объявляем функцию outerFunc, которая принимает параметр x. Внутри нее объявляется переменная y и функция innerFunc.
  2. Функция innerFunc имеет доступ к переменным x и y из внешней области видимости, а также к своему параметру z.
  3. outerFunc возвращает innerFunc, не вызывая ее.
  4. Мы вызываем outerFunc(5), она возвращает innerFunc. Эту возвращаемую функцию мы сохраняем в переменную closure.
  5. Затем мы вызываем closure(15), что по сути запускает innerFunc(15). Несмотря на то, что outerFunc уже завершила выполнение, innerFunc по-прежнему имеет доступ к x и y.
  6. innerFunc суммирует 5 (значение x), 10 (значение y) и 15 (значение z) и возвращает 30.

Таким образом, innerFunc «замкнула» в себе переменные x и y из внешней области видимости. Это и есть замыкание.

Область применения замыканий

Замыкания используются в JavaScript повсеместно. Вот несколько типичных сценариев:

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

function createCounter() {
  let count = 0;

  return {
    increment: function() {
      count++;
    },

    getCount: function() {
      return count;
    }
  };
}

const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2
console.log(counter.count); // undefined

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

  1. Фабричные функции. Замыкания позволяют создавать функции с предопределенными параметрами.

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

const add5 = makeAdder(5);
const add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

Здесь makeAdder — фабрика, создающая функции, прибавляющие заданное число x.

  1. Callback-функции. Замыкания часто используются в callback-функциях, чтобы они имели доступ к внешним переменным.

function fetchData(query, callback) {
  fetch(`https://api.com/${query}`)
    .then(response => response.json())
    .then(data => callback(data))
}

let results = [];
fetchData('users', data => {
  results = data;
  console.log(results); // данные загружены и доступны
});

Здесь callback имеет доступ к переменной results благодаря замыканию.

Проблемы замыканий

Замыкания очень мощный инструмент, но есть несколько моментов, которые надо учитывать:

  1. Утечки памяти. Поскольку замыкания хранят ссылки на переменные, это предотвращает их сборку мусорщиком. Если неграмотно использовать замыкания, это может привести к утечкам памяти.
  2. Непредсказуемое поведение в циклах. Новички часто допускают ошибку при создании замыканий в циклах:

for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); 
  }, 1000);
}
// через 1 секунду выведет 3, 3, 3

Так происходит потому что callback-функции в setTimeout замыкают одну и ту же переменную i, которая к моменту их вызова имеет значение 3. Чтобы этого избежать используйте let в цикле или создавайте дополнительное замыкание:


for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j);
    }, 1000);
  })(i);
}
// через 1 секунду выведет 0, 1, 2

Заключение

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

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

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

Поделиться записью

Telegram
WhatsApp
VK
Facebook
Email

Рекомендуем