Программирование и библиотеки.

Замыкания в JavaScript — это просто

Александр » 16 июл 2014, 02:57

Замыкание в JavaScript — это явление, когда вызывается функция, определенная внутри другой (родительской) функции, после того как родительская функция уже завершила свою работу. При этом в вызываемой функции остаются доступными все локальные переменные (область видимости, контекст исполнения) родительской функции.
Замыкания в JavaScript действительно достаточно просто реализуются, причём настолько, что порой создаются неумышленно программистами там, где они не нужны, даже вредны.
Как известно, в JavaScript переменные могут быть глобальными (точнее свойствами объекта window) и локальными (объявлена внутри функции с ключевым словом var. Если внутри функции объявить переменную, не используя ключевое слово var, то такая переменная станет глобальной).
Если функция объявлена в теле другой функции, то ей будут доступны все локальные переменные родительской функции. Если внутри этой дочерней функции объявить еще одну вложенную функцию, то этой вложенной функции будут доступны переменные (область видимости) всех родительских функций по цепочке вверх.
В посте Способы создания функций, область видимости функций и переменных я рассказывал некоторые особенности реализации функций в JavaScript, может быть полезным почитать перед продолжением этого поста о Замыканиях.
Вот простой пример создания замыкания:
Код: Выделить всё
function test() {
  var counter = 0;
  return function() {
    return ++counter;
  }
}
var fn1 = test();
var fn2 = test();
alert(fn1());  // 1
alert(fn1());  // 2
alert(fn2());  // 1
alert(fn2());  // 2

На этом примере очень хорошо видны свойства замыканий:
  1. Замыкания можно и нужно использовать для создания статических переменных.
  2. Контекст выполнения для каждого экземпляра функции уникален.
Нужно заметить, что если возвращаемая функция будет создана при помощи конструктора 'new Function', то она не будет иметь доступ к локальной области видимости, все переменные такая функция будет брать из глобальной области:
Код: Выделить всё
var counter=10;   
function test() {
  var counter = 0;
  return new Function('','return ++counter');
}
var fn1 = test();
var fn2 = test();
alert(fn1());  // 11
alert(fn1());  // 12
alert(fn2());  // 13
alert(fn2());  // 14


Помимо создания статических переменных, замыкания используются для создания приватных переменных объекта (инкапсулирование):
Код: Выделить всё
function test(name) {
  var greet = 'word';
  return {
    hello: function() {greet='Hello ' + name;},
    bye: function() {greet='Goodbye ' + name;},
    show: function() {alert(greet);}
    };
}
var myObj1 = test('Alex');
var myObj2 = test('Anna');

myObj1.show();  // word

myObj1.hello();
myObj2.hello();
myObj1.show();  // Hello Alex
myObj2.show();  // Hello Anna
myObj1.bye();
myObj2.bye();
myObj1.show();  // Goodbye Alex
myObj2.show();  // Goodbye Anna

Как видим, переменная greet не доступна напрямую как свойство объекта, но ей можно манипулировать через публичные методы hello, bye, show.

Для корректной работы с замыканиями важно помнить об области видимости переменных и о том как интерпретатор JavaScript обрабатывает код (фаза объявления и фаза инициализации переменных). Об этом я писал в посте Способы создания функций, область видимости функций и переменных.
Вот пример, где непонимание того, в какой момент происходит инициализация переменной во внутренней функции, приводит к неожиданному результату.
Код: Выделить всё
var arr = new Array();
for (var i = 0; i < 10; i++) {
   arr[i] = function() {
      alert(i);
   }
}
arr[5]();  //  10

При вызове любой функции из массива внутренняя переменная будет иметь значение, присвоенное в конце цикла: 10.
Для решения этой проблемы мы можем создать вспомогательную функцию-замыкание, сразу её запустив с параметром i, выполнив таким образом динамическое присваивание значения переменной i.
Код: Выделить всё
var arr = new Array();
for (var i = 0; i < 10; i++) {
   arr[i] = function(i) {
     return function(){ alert(i);}
        }(i);
}
arr[5]();  //  5

Также хочется заметить, что следует внимательнее создавать программы на JavaScript, чтобы избегать создания случайных замыканий, которые могут приводить к замедлению работы интерпретатора из-за размножения бесполезных объектов, которые не сможет удалить сборщик мусора, и прочим "утечкам памяти".
Александр
 
Сообщения: 397
Зарегистрирован: 20 мар 2014, 17:05

Вернуться в JavaScript