# ООП в функциональном стиле.

## Создание объекта

Для создания объектов используются функции-конструкторы. Они создают новые объекты определенного типа, который совпадает с именем функции:

```javascript
function Person(){
}

var person = new Person()
```

По всеобщей договоренности, функции-конструкторы именуются с большой буквы (`Person`). Для создания нового объекта используется оператор `new`, который создает пустой объект, заносит в него
определенное множество технической информации и передает его как `this` в конструктор:


```javascript
function Person(name, surname){
    this.name    = name
    this.surname = surname
}

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

Обратите внимание, что конструктор ничего не возращает, используя `return`. Считается что он возвращает новый объект, для этого достаточно просто заполнить нужные поля в `this`.

## Методы

Так же как данные, мы можем задать определенные методы объекту:

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

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

var person        = new Person("Ivan", "Petroff")
alert(person.getFullName())

person.fatherName = "Sydorych"
alert(person.getFullName())
```

## Приватные методы и данные; Замыкания.

**JS** не предоставляет обычных для Объектно-ориентированных языков программирования возможностей ограничения доступа к полям объекта (`private`, `public`, `protected`), для этого используется 
другой подход - так называемые *замыкания*.

```javascript
function Person(name, surname){

    this.name    = name
    this.surname = surname

    var originalFullName = name + " " + surname

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

    this.getOriginalName = function(){
        return name
    }

    this.getOriginalSurname = function(){
        return surname
    }

    this.getOriginalFullName = function(){
        return originalFullName
    }
}

var person        = new Person("Ivan", "Petroff")
person.name       = "John"
person.surname    = "Doe"
alert(person.getFullName())

alert(person.getOriginalName())
alert(person.getOriginalFullName())
```

Пример выше иллюстрирует этот подход: методы объекта `Person`, например `getOriginalFullName`, имеют доступ к области видимости уже *завершенной* функции. Код же *снаружи*, то есть определенный
вне конструктора `Person` никак не может получить значения переменных, определенных внутри конструктора (например `originalFullName`). Обратите внимание, что `this.name` и `name` - это **разные переменные**.
Одна из них является частью новосозданного объекта, вторая же - параметр функции `Person` в её области видимости. Таким образом, единственным способом работы с переменными в замыкании является *код*,
который находится в одном *лексическом контексте* с переменными:

### геттеры и сеттеры.

```javascript
function Person(name, surname){

    this.name    = name
    this.surname = surname

    var age      = 0

    this.setAge = function(newAge){
        newAge = +newAge
        if (!isNaN(newAge) && Number.isInteger(newAge) && (newAge > 0) && (newAge < 100)){
            age = newAge
        }

        return age
    }

    this.getAge = function(){
        return age
    }
}

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

alert(person.setAge(50))
alert(person.setAge(125))
alert(person.setAge(25))
```

Таким образом реализуется **паттерн getter/setter** - специальных функций, которые читают и записывают данные, защищенные от записи внешним кодом, проверяя их на правильность при записи.

### Приватные функции в замыкании

Таким же способом мы можем определить функции для внутреннего использования, недоступные через объект, но доступные через методы объекта:

```javascript
function Person(name, surname){

    this.name    = name
    this.surname = surname

    function getFullShortName(){
        return this.name + " " + this.surname
    }

    function getFullLongName(){
        return this.name + " " + this.fatherName + " " +this.surname
    }

    this.getFullName = function (){
        if ("fatherName" in this){
            return getFullLongName()
        }
        else {
            return getFullShortName()
        }
    }
}

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

alert(person.getFullName())
```

Данный пример не работает, так как `this` *не является* частью области видимости Person, а устанавливается в зависимости от *контекста вызова*. Общее правило таково: `this` равен тому, что написано до точки:

```javascript
alert(person.getFullName()) //this == person
```

```javascript
...

    function getFullShortName(){
        return this.name + " " + this.surname //oups
    }

    function getFullLongName(){
        return this.name + " " + this.fatherName + " " +this.surname //oups
    }

    this.getFullName = function (){

        if ("fatherName" in this){
            return getFullLongName(); //this == window или undefined в strict
        }
        else {
            return getFullShortName(); //this == window или undefined в strict
        }
    }
...
```

Для исправления ситуации воспользуемся методом `call` объекта `Function`, который позволяет "подсунуть" функции другой `this`:

```javascript
function Person(name, surname){

    this.name    = name
    this.surname = surname

    function getFullShortName(){
        return this.name + " " + this.surname
    }

    function getFullLongName(){
        return this.name + " " + this.fatherName + " " +this.surname
    }

    this.getFullName = function (){
        if ("fatherName" in this){
            return getFullLongName.call(this) //передаем текущий объект person как this в getFullLongName
        }
        else {
            return getFullShortName.call(this)
        }
    }
}

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

alert(person.getFullName())
```

Альтернативой этому решению является *сохранение* `this` в замыкании:


```javascript
function Person(name, surname){

    this.name    = name
    this.surname = surname

    var me = this; //теперь у нас есть ссылка на текущий объект в замыкании

    function getFullShortName(){
        return me.name + " " + me.surname
    }

    function getFullLongName(){
        return me.name + " " + me.fatherName + " " + me.surname
    }

    this.getFullName = function (){
        if ("fatherName" in this){
            return getFullLongName() 
        }
        else {
            return getFullShortName()
        }
    }
}

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

alert(person.getFullName())
```

В отличие от `this`, переменная `me` всегда будет указывать на `this`, который был при создании объекта (если, конечно, вы не поменяете значение `me`)

## `call` и `apply`

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

```javascript
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` принимает массив в качестве набора параметров функции помогает **творить чудеса**:

```javascript
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()
```

## Рекурсия

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

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

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

N ! = N x (N - 1) !


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

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

### Деревья

Рекурсия удобна для обработки вложенностей. 


```javascript
var tree = 
{
    name: "root",
    nested: [
        {
            name: "0",
            nested: [
                {
                    name: "00",
                    nested: [
                        {
                            name: "000",
                        },
                        {
                            name: "001",
                        },
                        {
                            name: "002",
                        },
                    ]
                },
                {
                    name: "01",
                },
                {
                    name: "02",
                },
            ]
        },
        {
            name: "1",
            nested: [
                {
                    name: "10",
                },
                {
                    name: "11",
                },
                {
                    name: "12",
                },
            ]
        },
        {
            name: "2",
            nested: [
                {
                    name: "20",
                },
                {
                    name: "21",
                },
                {
                    name: "22",
                },
            ]
        },
    ]
}

function walker(node, nestedFieldName, deepness, callback){
    callback(node, nestedFieldName, deepness);
    if (nestedFieldName in node){
        for (key in node[nestedFieldName]){
            walker(node[nestedFieldName][key], nestedFieldName, deepness +1, callback)
        }
    }
}

var str = "";
walker(tree, "nested", 0, function(node, nestedFieldName, deepness){
    str += "  ".repeat(deepness) + node.name + "\n";
});

console.log(str);
```