Инкапсуляция с помощью замыканий в JavaScript
Одной из необычных особенностей JavaScript является инкапсуляции с помощью замыканий. Необычной потому, что во многих других языках программирования этот механизм реализован с помощью классов. В JavaScript же другой механизм, который может показаться странным, если не разобраться в особенностях переменных и функций.
Если переменная или объект в JavaScript не помещены внутрь какой-либо функции, то они становятся глобальными. Все глобальные элементы в JavaScript являются свойствами глобального объекта. Для браузера это объект window. При этом конструкции for, if и другие не влияют на видимость переменных.
Создание глобальных переменных, как правило, нежелательно, так как оно может привести к трудно обнаружимым ошибкам, и усложняет перенос кода в другие приложения.
Например, если написать библиотеку следующего содержания foo.js:
var a ="Привет";
function Hello2(){
alert(a);
}
Hello2();
А потом подключить её к проекту:
<script>
var a = "Пока";
</script>
<script src="foo.js"></script>
<script>
alert(a);
</script>
То переменная из библиотеки перезапишет начальное значение переменной, и в результате в последнем alert, будет выведено Привет, а не Пока. Принято минимизировать использование глобальных переменных, скрывая(инкапсулируя) все данные в классах.
Инкапсуляция - это помещение всех данных и функций, объединенных одним смыслом в единую сущность. При этом не нужные для пользователя данные скрываются внутри сущности и доступны только внутри неё.
В объектно-ориентированных языках, инкапсуляция достигается благодаря применению модификаторов доступа. В JavaScript для организации инкапсуляции применяются замыкания.
Замыкание - это функция + все внешние переменные, которые ей доступны.
Доступ к переменных в функциях осуществляется следующим образом. Все переменные внутри функции в JavaScript - это свойства объекта LexicalEnvironment, так называемое лексическое окружение. Данный объект является внутренним и к нему нет доступа.
Кроме того, в специальном внутреннем свойстве у функции хранится объект [[scope]]. В нём хранятся внешние для данной функции переменные. Функция сначала ищет переменные в LexicalEnvironment и если не находит, ищет в [[scope]]. Таким образом, функция сначала ищет объявления нужных элементов внутри себя, и лишь потом обращается к внешним элементам. При этом из внешней области берётся именно текущее значение переменной. Кроме того, в JavaScript внутри функций можно объявлять и другие функции.
Ярким примером замыканий может служить код счётчика:
function useCounter() {
var Count = 1;
return function() {
return Count++;
};
}
var counter1 = useCounter();
alert(counter1()); // 1
alert(counter1()); // 2
alert(counter1()); // 3
var counter3 = useCounter();
alert(counter3()); // 1
Благодаря замыканиям, внутренняя функция имеет доступ к переменной Count и может возвращать его значения во внешний код, в тоже время пользователи функции useCounter не имеют прямого доступа к переменной Count, для них она не видима. Благодаря применению замыканий, переменная Count оказалась скрыта внутри функции useCounter.
Аналогично можно создать и объект, который будет скрывать переменную с помощью замыканий:
function Count2() {
var count = 1;
return {
getValue: function() {
return count ++;
},
setValue: function(value) {
count = value;
},
reset: function() {
count = 1;
}
};
}
var counter33 = Count2();
alert(counter33.getValue()); // 1
alert(counter33.getValue()); // 2
counter33.setValue(7);
alert(counter33.getValue()); // 7
Таким образом замыкания можно использовать и внутри объектов.
Для реализации модулей можно использовать мгновенно вызванную функцию (immediately invoked function):
(function() {
var msg = "hi";
function showmsg () {
alert(msg);
}
// выводим сообщение
showmsg();
})();
В данном коде переменная msg доступна внутри функции showmsg, благодаря замыканию, а благодаря использованиюмгновенно вызванной функции, переменные не попадают в глобальный контекст.