10Closures.md 4.8 KB

Замыкания, приватные методы и данные.

Функция в JS находится сразу в двух контекстах: динамическом (this, значения параметров) и лексическом (переменные из более высоких областей видимости в месте определения функции). Динамический контекст - это контекст вызова функции - значение параметров и окружение на момент вызова; лексический контекст - контекст определения функции, её вложенности в другие области видимости, доступ к которым функция имеет и после окончания выполнения функций-владельцев этих областей видимости.

makeAdder

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.

makeCounter


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());