08.md 7.2 KB

ООП функциональное. Замыкания

call и apply

Эти два метода объекта Function позволяют вызвать функцию, указав this и параметры:

function Person(name, surname){
    this.name    = name
    this.surname = surname

    this.getFullName = function(){
        return this.name + (this.fatherName ? " " + this.fatherName + " " : " ") + this.surname
    }
}

function say(greet, aftergreet){
    alert(greet + " " + this.getFullName() + aftergreet)
}

var person        = new Person("Ivan", "Petroff")

say.call(person,"Hello", "!!111!!!")
say.apply(person,["Hi", "by apply"]) //feel the difference

То, что apply принимает массив в качестве набора параметров функции помогает творить чудеса:

var someArray = [1,5,7,-17,100500];

Math.min(someArray) //Math.min так не умеет
Math.min(1,5,7,-17,100500,-123) // зато умеет так
Math.min.apply(Math,someArray) // ...и мы этим воспользуемся...

// или же сделаем свой min в someArray:

someArray.min = function(){
    return Math.min.apply(Math,someArray);
}

someArray.min()

// или же сделаем свой min для всех массивов 

Array.prototype.min = function(){ 
    return Math.min.apply(Math, this) 
};



[100, 500,  -100500].min()

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

Функция в 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.


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

Рекурсия и её отладка

Рекурсия - это прямой или косвенный самовызов функции. Обычно применяется для обработки деревьев в структурах данных или иных вложенностей. Например факториал (!) - произведение всех чисел от 1 до N можно определить как:

N ! = 1 x 2 x 3 x 4 x .... x N,

или рекурсивно:

N ! = N x (N - 1) !

function factorial(N){
    if (N <= 1){
        return 1; 
    }
    return N * factorial(N -1);
}

factorial ( 5 ); // это 5 * factorial(4), что, в свою очередь, будет 4 * factorial(3) и так далее

При отладке рекурсий обращайте внимания на вложенность вызовов (Call Stack) и областей видимостей (Scopes).