Замыкания в JavaScript

Если вы используете JavaScript, но при этом так до конца и не разобрались, что же это за чудная штука такая - замыкания, и зачем она нужна - эта статья для вас

» » Категория: WEB-Разработка / HTML, CSS, PHP, JScript

Замыкания в javascript - пример замыканий javascript функций
Замыкания в javascript - пример замыканий javascript функций

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

Как известно, в JavaScript областью видимости локальных переменных (объявляемых словом var) является тело функции, внутри которой они определены.

Если вы объявляете функцию внутри другой функции, первая получает доступ к переменным и аргументам последней:

Код: Выделить всё Развернуть
function outerFn(myArg) {
    var myVar;
    function innerFn() {
         //имеет доступ к myVar и myArg
    }
}

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

Рассмотрим пример - функцию, возвращающую кол-во собственных вызовов:

Код: Выделить всё Развернуть
function createCounter() {
    var numberOfCalls = 0;
    return function() {
         return ++numberOfCalls;
    }
}
var fn = createCounter();
fn(); //1
fn(); //2
fn(); //3

В данном примере функция, возвращаемая createCounter, использует переменную numberOfCalls, которая сохраняет нужное значение между ее вызовами (вместо того, чтобы сразу прекратить свое существование с возвратом createCounter).

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

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

Упростим немножко пример выше - уберем необходимость отдельно вызывать функцию createCounter, сделав ее аномимной и вызвав сразу же после ее объявления:

Код: Выделить всё Развернуть
var fn = (function() {
    var numberOfCalls = 0;
    return function() {
         return ++ numberOfCalls;
    }
})();

Такая конструкция позволила нам привязать к функции данные, сохраняющиеся между ее вызовами - это одно из применений замыканий. Иными словами, с помощью них мы можем создавать функции, имеющие свое изменяемое состояние.

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

Код: Выделить всё Развернуть
var createHelloFunction = function(name) {
    return function() {
         alert('Hello, ' + name);
    }
}
var sayHelloHabrahabr = createHelloFunction('Habrahabr');
sayHelloHabrahabr(); //alerts «Hello, Habrahabr»

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

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

Рассмотрим чуть более сложный пример - метод, привязывающий функцию к определенному контексту (т.е. объекту, на который в ней будет указывать слово this).

Код: Выделить всё Развернуть
Function.prototype.bind = function(context) {
    var fn = this;
    return function() {
         return fn.apply(context, arguments);
    };
}
var HelloPage = {
    name: 'Habrahabr',
    init: function() {
         alert('Hello, ' + this.name);
    }
}
//window.onload = HelloPage.init; //алертнул бы undefined, т.к. this указывало бы на window
window.onload = HelloPage.init.bind(HelloPage); //вот теперь всё работает

В этом примере с помощью замыканий функция, вощвращаемая bind"ом, запоминает в себе начальную функцию и присваиваемый ей контекст.

Следующее, принципиально иное применение замыканий - защита данных (инкапсуляция). Рассмотрим следующую конструкцию:

Код: Выделить всё Развернуть
(function() {
    …
})();

Очевидно, внутри замыкания мы имеем доступ ко всем внешним данным, но при этом оно имеет и собственные. Благодаря этому мы можем окружать части кода подобной конструкцией с целью закрыть попавшие внутрь локальные переменные от доступа снаружи. (Один из примеров ее использования вы можете увидеть в исходном коде библиотеки jQuery, которая окружает замыканием весь свой код, чтобы не выводить за его пределы нужные только ей переменные).

Есть, правда, одна связанная с таким применением ловушка - внутри замыкания теряется значение слова this за его пределами. Решается она следующим образом:

Код: Выделить всё Развернуть
(function() {
    //вышестоящее this сохранится
}).call(this);

Рассмотрим еще один прием из той же серии. Повсеместно популяризовали его разработчики фреймворка Yahoo UI, назвав его «Module Pattern» и написав о нем целую статью в официальном блоге.
Пускай у нас есть объект (синглтон), содержащий какие-либо методы и свойства:

Код: Выделить всё Развернуть
var MyModule = {
    name: 'Habrahabr',
    sayPreved: function(name) {
         alert('PREVED ' + name.toUpperCase())
    },   
    sayPrevedToHabrahabr: function() {
         this.sayPreved(this.name);
    }
}
MyModule.sayPrevedToHabrahabr();

С помощью замыкания мы можем сделать методы и свойства, которые вне объекта не используются, приватными (т.е. доступными только ему):

Код: Выделить всё Развернуть
var MyModule = (function() {
    var name = 'Habrahabr';
    function sayPreved() {
         alert('PREVED ' + name.toUpperCase());
    }
    return {
         sayPrevedToHabrahabr: function() {
             sayPreved(name);
         }
    }
})();
MyModule.sayPrevedToHabrahabr(); //alerts «PREVED Habrahabr»

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

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

Код: Выделить всё Развернуть
for (var i = 0; i < links.length; i++) {
    links[i].onclick = function() {
         alert(i);
    }
}

На деле же оказывается, что при клике на любую ссылку выводится одно и то же число - значение links.length. Почему так происходит? В связи с замыканием объявленная вспомогательная переменная i продолжает существовать, при чем и в тот момент, когда мы кликаем по ссылке. Поскольку к тому времени цикл уже прошел, i остается равным кол-ву ссылок - это значение мы и видим при кликах.

Решается эта проблема следующим образом:

Код: Выделить всё Развернуть
for (var i = 0; i < links.length; i++) {
    (function(i) {
         links[i].onclick = function() {
             alert(i);
         }
    })(i);
}

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

Вот и все. Эта статья, конечно, не претендует на звание исчерпывающей, но кому-нибудь, надеюсь, все-таки поможет разобраться.

© habrahabr.ru

зы
Для сохранений между вызовами проще использовать func_name.attr типа:

Код: Выделить всё Развернуть
function countIt(reset) {
if (reset ||! countIt.cnt) countIt.cnt = 0;
return countIt.cnt++;
}
  • 5.00
0 комментариевПросмотров: 1962 | Теги: JavaScriptРазработка