Замыкания в javascript, область видимости переменной в js
Когда вызывается функция, интерпретатор javascript устанавливает область видимости в соответствии с цепочкой областей видимости, которая действовала на момент ОПРЕДЕЛЕНИЯ функции. Затем интерпретатор js добавляет новый объект (объект вызова). В этом объекте вызова сохраняются все именованные аргументы функции, локальные переменные, определенные внутри функции (в тему: поднятие в javascript).
Когда вызывается функция main
, ее цепочка областей видимости содержит объект вызова, за которым идет глобальный объект. Когда вызывается функция inner
, ее цепочка областей видимости включает в себя ТРИ объекта: собственный объект вызова, объект вызова функции main и глобальный объект.
javascript
function main() {
var num = 0;
return function inner() {
return ++num;
}
}
var fn = main();
alert(fn()); // 1
alert(fn()); // 2
В обычной ситуации, когда функция завершает работу, то объект вызова удаляется из цепочки вызова. Однако для вышеописанной ситуации все не так:
Если ссылка на вложенную функцию сохраняется в глобальной области видимости (это происходит, когда вложенная функция передается в виде возвращаемого значения объемлющей функции), то появляется внешняя ссылка на вложенную функцию (при этом вложенная функция продолжает ссылаться на объект вызова объемлющей функции). И выходит, что при каждом вызове объемлющей функции сохраняются все объекты вызова и локальные переменные функции.
Область видимости переменной в js
var t='Глобальная переменная';
//alert(t); Глобальная переменная
function t_inc(){return 'это значение из функции(возвращаемое)';}
t = t_inc();
//alert(t); это значение из функции(возвращаемое)
function t_chng(){t='Меняем значение глобальной переменной t в функции t_chng();';}
t_chng();
//alert(t); Меняем значение глобальной переменной t в функции t_chng()
function t_arg(arg)
{
var t; /* тут объявил тоже t, но область её видимости в теле функции */
t = arg;
return t;
}
t_arg('Для проверки');
// alert(t); глобальная t не изменилась:
// "Меняем значение глобальной переменной t в функции t_chng()"
t=t_arg('Это решил передать значение через аргуемнт функции');
// alert(t); Это решил передать значение через аргуемнт функции
Переменные объявленные внутри функции являются локальными. Локальные переменные являются свойствами специального объекта – LexicalEnvironment
(объект переменных).
Когда вызывается какая-либо функция, то создается объект LexicalEnvironment
куда заносятся переменные функции, вложенные функции, например.
javascript
function getDecimal(num){ // f.[[Scope]] = window
// P.S. все аргументы функции по умолчанию являются локальными переменными
// LexicalEnvironment = { num:4, n: undefined, d: undefined} -> window
var n = parseInt(num);
// LexicalEnvironment = { num:4, n: 4, d: undefined}
var d = num - n;
// LexicalEnvironment = { num:4, n: 4, d: 0.2}
return d;
// LexicalEnvironment удаляется
}
getDecimal(4.2);
После того как функция заканчивается (код функции выполняется), объект LexicalEnvironment
удаляется, так как больше не нужен, при этом память освобождается.
Кроме локальных переменных функция может использовать переменные объявленные вне функции. Это работает так: у функции есть свойство scope
; функция всегда объявляется в каком-то контексте (например в глобальном объекте window
); в свойстве функции scope
есть ссылка на этот объект (window
); свойство scope
привязывается к функции в момент ее создании (и оно неизменно); Объекту LexicalEnvironment
добавляется специальная ссылка (ссылка на внешнее окружение) и ее значение ставится по scope
функции, например, window
. Когда идет поиск переменной, то изначально она ищется в объекте LexicalEnvironment
, а, если там ее нет, ищется по ссылке (window
).
Функция ищет переменные сначала у себя, а потом в том объекте, в котором она СОЗДАВАЛАСЬ, а НЕ в том, где она ВЫПОЛНЯЕТСЯ.
Бывают ситуации, когда объект переменных(LexicalEnvironment) не уничтожается. Например, если мы хотим, чтобы результатом sayHi
было не приветствие, а функция, которая это приветствие выводит. Это может пригодится, например, если требуется вызвать приветствие не сразу, а при каком-то условии (при нажатии на кнопку).
javascript
function sayHi(name,age){ // [[scope]] = window
// LexicalEnvironment = {
// name: 'Петька',
// age: 18,
// makePhrase: function
// chooseWord: function
//} -> window
// во время выполнения скрипта создается и возвращается функция:
// при создании функции, функция получает
// [[scope]] = LexicalEnvironment (приведен выше)
return function work(){
// LE = { } -> LexicalEnvironment
alert( makePhrase(name) );
};
// при этом неважно, как вы объявили ф-ю
// как function expression (как в данном случае)
// или как function declaration
// -----------
function makePhrase(){
return chooseWord() + ', ' + name;
}
function chooseWord(){
return age > 18 ? 'Здравствуйте' : 'Привет';
}
}
var a = sayHi('Петя', 18);
var b = sayHi('Василь Иваныч', 58);
a(); // Привет, Петя
b(); // Здравствуйте, Василь Иваныч
Если мы удалим функцию sayHi
: sayHi = null;
Функции a
и b
по-прежнему будут работать. РАБОТАЮТ ОНИ БЛАГОДАРЯ ТОМУ, ЧТО JAVASCRIPT ПОДДЕРЖИВАЮТ ЗАМЫКАНИЯ.
Как правило объект переменных функции уничтожается, но в данном случае на объект переменных есть ссылка: функция, которая возвращена во внешний код, как work
, ссылается через свое свойство scope
на объект переменных,
javascript
// LexicalEnvironment = {
// name: 'Петька',
// age: 18,
// makePhrase: function
// chooseWord: function
//}
поэтому он остается в памяти. Это объект держит в памяти ссылка: функция work
.
Что происходит при запуске функции work
:
Интерпретатор javascript создает для функции work
свой объект переменных, он будет пустой поскольку ни переменных, ни аргументов там нет. Но у него будет ссылка на внешний объект переменных, который ставится по значению scope
. Поэтому при поиске функции makePhrase
, не найдя ее в самой функции work
, интерпретатор найдет ее во внешнем объекте. name
также возьмет из внешнего объекта.
Что такое замыкание?
ЗАМЫКАНИЕМ называется функция вместе со всеми переменными которые ей доступны.
В замыкание для функции work будет входить сама функция, объект переменных (LexicalEnvironment
) и внешний объект переменных(window
). Все это вместе будет формировать замыкания. Доступ к переменной, которая не находится в самой функции, а находится во внешнем объекте переменных называется ДОСТУП ЧЕРЕЗ ЗАМЫКАНИЕ.
Примеры замыканий
Функция, которая создает счетчик
javascript
function makeCounter(){
var currentCount = 0;
return function(){
currentCount++;
return currentCount;
}
}
var counter = makeCounter();
//каждый вызов увеличивает счетчик
console.log(counter()); //1
console.log(counter()); //2
console.log(counter()); //3
Функция makeCounter
при запуске возвращает новую функцию. Она записывается в переменную counter
. Эта функция берет значение переменной currentCount
из внешнего объекта переменных и увеличивает его, затем возвращает. При новом запуске опять увеличивает и возвращает.
Благодаря замыканию функция, которая возвращается, получает свою конкретную область, в которой она может что-то хранить.
Еще один пример замыкания:
Для создания неисчезающей переменной можно использовать замыкания (между вызовами функция запоминает некоторое значение)
javascript
var un = (function(){
var id = 0;
return function(){ return id++; }
})();
un();
un();
un();
un();
un();
un();
un(); // 6
Неявное замыкание
Стоит отметить важную деталь: все переменные, объявленные внутри фукнции (при помощи ключевого слова var
) являются локальными и видны только внутри этой функции. Или проще: если мы объявим какую-то переменную внутри функции, то вне этой функции доступа к этой переменной у нас не будет.
Переменная local
, расположенная внутри функции, связана с внешней переменной local
, так как внутри функции перед переменной local
не используется ключевое слово var
(если бы внутри функции перед переменной local
использовалось ключевое слово var
, то переменная local
, расположенная внутри функции, была бы локальной и никак бы не связывалась с переменной local
объявленной до функции).
А так у нас переменная local
не перестает существовать, так как local
внутри функции это глобальная переменная (то есть та, котороая объявлена за пределами функции).
js
var local =1;
window.setInterval(
function(){
console.log( new Date() + " local="+local );
local++;
},
1000
);
/*
......
Fri Oct 11 2013 17:59:01 GMT+0400 local=132
Fri Oct 11 2013 17:59:02 GMT+0400 local=133
Fri Oct 11 2013 17:59:03 GMT+0400 local=134
Fri Oct 11 2013 17:59:04 GMT+0400 local=135
Fri Oct 11 2013 17:59:05 GMT+0400 local=136
Fri Oct 11 2013 17:59:06 GMT+0400 local=137
Fri Oct 11 2013 17:59:07 GMT+0400 local=138
Fri Oct 11 2013 17:59:08 GMT+0400 local=139
Fri Oct 11 2013 17:59:09 GMT+0400 local=140
Fri Oct 11 2013 17:59:10 GMT+0400 local=141
.....
*/
Частные члены через замыкание
Замыкания можно использовать для определения частных членов. Например, функция-конструктор может создавать замыкание и любые переменные, определенные в функции-конструкторе и при этом ставшие частью замыкания, не будут доступны за пределами объекта.
js
function Name(){
// частная переменная
var name = "Vasy";
// привилегированный метод -
// ф-я, дающая доступ к частным членам
this.getName = function(){
return name;
}
}
var people1 = new Name();
console.log(people1.name); // undefined
//обращаемся к частной переменной через
//привилегированный метод
people1.getName(); // Vasy
Комментарии к статье