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

Тема данной статьи области видимости и замыкания. Начнем с областей видимости, а точнее использования ключевого слова «var». Рассмотрим следующие примеры:

a = "Hello";

alert("a="+a);


и

var a = "Hello";

alert("a="+a);


На первый взгляд никакой разницы нет и результат вывода одинаковый. Так зачем тогда вообще писать «var»? Рассмотрим случай когда те же операции выполняются внутри функции:

function Scope() {

a = "Hello";

var b = "Hello";

alert("a="+a);

alert("b="+b);

alert("window.a="+window.a);

alert("window.b="+window.b);

}

Scope();


Вывод данной программы

a=Hello

b=Hello

window.a=Hello

window.b=undefined



дает понять, что разница между объявлением с и без var все же есть. Если быть точным переменная объявленная без использования var становится доступна в объекте window. То есть, a=1 эквивалентно window.a=1, и префикс window. можно опускать.



Следует, однако, помнить, что при попытке получить значение не объявленной переменной, например, alert(undeclared_var), интерпретатор выдает ошибку, а при обращении к неопределенному ключу массива выводит "undefined", как, например, alert(window.undeclared_var).



Теперь ясно, что при помощи var создаются локальные переменные, внутри функции (их еще называют лексическими, потому что они «не видны» нигде снаружи относительно фигурных скобок функции). Переменные же объявленные без var создаются в глобальном массиве window.

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

function Scope() {

/*some code*/

for(var i=0;i<10;i++)

{

var a = "Hello";

}

alert("i="+i);

alert("a="+a);

}

Scope();


Вывод данной программы:

i=10

a=Hello


То есть, при объявлении переменной через var, она становится доступна во всей функции, а не только в блоке кода.


Замыкания в JavaScript



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



Рассмотрим, например, обычное объявление функции:

function hello(world) { alert("Hello, "+world) }


на самом деле, в JavaScript он эквивалентен:

hello = function(world) { alert("Hello, "+world) };


То есть, созданию функции во время выполнения программы (на этапе присваивания).



На первый взгляд может показаться, что разница между созданием функции в операторе присваивания и стандартным образом невелика. Если бы оператор function всегда создавал именно функции, разницы бы, конечно, не было. Но, на самом деле, оператор function в JavaScript создает не функцию, а замыкание.



Замыкание — это совокупность функции и всех ее лексических переменных, из содержащего ее контекста, которые она использует.



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

// Создает одну функцию, которая выводит факториал входного параметра.

function makeFactorial(n) {

return function() {

var result = 1;

for(var j=1; j<n; j++)

result = result*(j+1);

alert(result);

};

}

// Создает и возвращает массив таких функций.

function makeFunctionArray(length) {

var arr = [];

for (var i=0; i<length; i++) {

arr[i+1] = makeFactorial(i+1);

}

return arr;

}

// Создадим массив

var factorials = makeFunctionArray (10);

// И вызовем функцию вычисляющую факториал 2.

factorials[2]();


В этом примере, в функции makeFactorial(), происходит вызов function и создается замыкание, при этом переменная n из контекста содержащего функцию попадает в замыкание. Таким образом, при каждом вызове makeFactorial() с различными значениями n, создается новое замыкание (при этом функция остается та же), отличающееся значением лексической переменной n.



Нельзя ли упростить эту конструкцию? Например, так:

// Создает и возвращает массив таких функций.

function makeFunctionArray(length) {

var arr = [];

for (var i=0; i<length; i++) {

arr[i+1] = function() {

var result = 1;

for(var j=1;j<i+1;j++)

result = result*(j+1);

alert(result);

};

}

return arr;

}


Оказывается, что нельзя. Данный код создаст массив из length функций печатающих length!. В нашем случае, factorials[2](); выведет 3 628 800, то есть 10!. На первый взгляд это может показаться странным, но если вспомнить, что в JavaScript все переменные хранят ссылки на объекты, становится понятнее. Дело в том, что в замыкание попадает не сама переменная, а ссылка на нее. Таким образом, в последнем примере все 10 замыканий, созданных в цикле, будут работать с общей переменной i. В момент вызова функции значение переменной i, на которую ссылаются замыкания 10. В предыдущем же примере, лексическая переменная n создавалась при каждом вызове makeFactorial и в замыкание попадала она.



Мы кратко обсудили работу областей видимости и замыканий в JavaScript, на самом же деле тема эта достаточно сложная и для полного ее понимания, безусловно, необходима практика. Полагаю, эта статья дала вам общее представление о вопросе.
1

Комментарии

Для того, чтоб оставлять комментарии или зарегистрируйтесь.