Функция в JS находится сразу в двух контекстах: динамическом (this
, значения параметров) и лексическом (переменные из более высоких областей
видимости в месте определения функции). Динамический контекст - это контекст вызова функции - значение параметров и окружение на момент вызова;
лексический контекст - контекст определения функции, её вложенности в другие области видимости, доступ к которым функция имеет и после окончания
выполнения функций-владельцев этих областей видимости.
function makeAdder(x){
function adder(y){
return x + y;
}
return adder
}
var add5 = makeAdder(5);
var greeter = makeAdder("Hi, ");
alert(add5(2));
alert(greeter("John"));
В примере выше 5
,"Hi, "
, 2
и "John"
находятся в динамическом контексте вызова функции. А вот значение x
в adder
находится в лексическом
контексте. На момент запуска функций add5
и greeter
функции makeAdder
уже давно отработали, однако значение x
и область видимости продолжают
присутствовать в функциях add5
и greeter
. Это называется замыканием - данные в локальной области видимости отработавшей функции и код, который
использует эти данные впоследствии. В широком смысле замыкание является объектом, так как хранит в единой сущности код и данные.
В контексте JS замыкания очень удобны для огораживания кода, сохранения состояния переменных "на будущее", для функций обратного вызова. Так же замыкания удобны для организации приватных полей у объектов JS.
function makeCounter(){
var counter = 0;
return function(){
return counter++;
}
}
Функция выше создает счетчик, значение которого можно узнать из возвращаемой анонимной функции. При этом счетчик увеличится на 1. Если расширить функционал данного примера для чтения и декремента счетчика, получим, например:
function makeCounter(){
var counter = 0;
function inc(){
return counter++;
}
function dec(){
return counter--;
}
function read(){
return counter;
}
return [inc,dec,read];
}
Результатом выполнения makeCounter
будет массив функций. Однако намного более наглядным будет создание именованного массива, т. е. объекта:
function makeCounter(){
var counter = 0;
function inc(){
return counter++;
}
function dec(){
return counter--;
}
function read(){
return counter;
}
return {inc: inc, dec: dec, read: read};
}
... что во многом схоже с обычным объектом сделанным через функцию-конструктор:
function Person(name, surname){
this.name = name
this.surname = surname
var age = 0
function checkNewAge(){
return (!isNaN(this.newAge) && Number.isInteger(this.newAge) && (this.newAge > 0) && (this.newAge < 100))
}
this.setAge = function(newAge){
this.newAge = +newAge
if (checkNewAge()){
age = this.newAge;
}
return age
}
this.getAge = function(){
return age
}
this.getOriginalName = function(){
return name;
}
this.getOriginalSurname = function(){
return surname;
}
}
var person = new Person("Ivan", "Petroff")
alert(person.setAge(50))
alert(person.setAge(125))
alert(person.setAge(25))
person.name = "John";
person.surname = "Doe";
alert(person.getOriginalName());
alert(person.getOriginalSurname());