# Рекурсия. DOM, замыкания и функциональное ООП.
## Рекурсия
Рекурсия - это прямой или косвенный самовызов функции. Обычно применяется для обработки деревьев в структурах данных или иных вложенностей.
Например **факториал** (`!`) - произведение всех чисел от `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);
```
## Введение в **DOM**.
**DOM** (*Document Object Model*) - внутренние объекты и функции *браузерного* **JS**, которые позволяют работать с деревом тэгов текущей загруженной страницы. **JS** через это **API** имеет полный доступ ко
всему тому, что есть в HTML и CSS.
### Корень
Корнем дерева элементов **DOM** является объект `document`
### Поиск элементов
Что бы найти элемент, нужно обратится к методу `document`, или любого другого элемента, в который нужно что-то найти:
```javascript
var el = document.getElementById("someId"); //обратите внимания, без #
var el2 = document.querySelector("#someId"); //поиск по любому селектору, аналог jQuery
var el3 = document.querySelectorAll("a"); //поиск всех тэгов a
```
### Создание элементов **DOM**
```javascript
document.createElement("a"); //обратите внимание, без <>
```
Добавление элементов:
```javascript
var tr = document.createElement("tr");
var td = document.createElement("td");
var td2 = document.createElement("td");
tr.appendChild(td); //добавление ячейки в конец строки таблицы.
tr.insertBefore(tr.childNodes[0],td2); //добавление ячейки перед первой ячейкой (в самое начало строки таблицы)
```
Ссылка на родительский элемент находится в свойстве `parentElement`:
```javascript
tr.childNodes[0].parentElement == tr
```
### Свойства объектов или наборов объектов элементов в **DOM**
- `value` - **свойство** а не функция для значения поля ввода.
- `attributes` - объект `attributes` с атрибутами html-тэга. Также есть 4 функции для работы с атрибутами:
- `hasAttribute` - проверка на наличие атрибута
- `getAttribute` - чтение
- `setAttribute` - запись
- `removeAttribute` - удаление
- `style` - объект стиля элемента
- `innerHTML` - строка вложенного HTML в элементе.
- `innerText` - строка вложенного текста в элементе.
### События
Каждый элемент **DOM** содержит множество свойств `on...`, в которые вы можете занести тот или иной обработчик события:
```javascript
document.onmousemove = function(){
document.write("mouse move
");
}
```
Так же можно добавлять обработчики используя метод элемента `addEventListener`:
```javascript
document.addEventListener("mousemove",function(){
document.write("mouse move
");
});
```
### Нюансы
#### Элемент в дереве
может встречаться только один раз. Вы не можете вставить элемент дважды в дерево. Если вы хотите создать копию элемента в вашем DOM-дереве, используйте `cloneNode`.
#### HTML/CSS
Всё, что вы видели в HTML/CSS может быть установлено как свойства объекта-узла DOM и тут же будет отображено в браузере
#### `children` и `childNodes`
- Узлами (`Node`) может быть любой текст в HTML, в том числе обычный текст и тот или иной тэг. Дочерние элементы каждого элемента находятся в **псевдомассиве** `childNodes`
- В **псевдомассиве** `children` находятся только дочерние узлы-тэги, но без обычного текста.
![например](http://shots.asmer.org.ua/2017_10_12__17_50_35.png)
> **Задание**
> Сделайте любое предыдущее задание по конструированию **HTML** используя **DOM**, а не конструирование строки. Проанализируйте отличия.
## Замыкания, приватные методы и данные.
> One of the conclusions that we reached was that the "object" need not be a primitive notion in a programming language; one can build objects and their behaviour from little more than assignable value cells and good old lambda expressions.
>
> -— Guy Steele on the design of Scheme
> Closures are one of those few curious concepts that are paradoxically difficult because they are so simple. Once a programmer becomes used to a complex solution to a problem, simple solutions to the same problem feel incomplete and uncomfortable. But, as we will soon see, closures can be a simpler, more direct solution to the problem of how to organise data and code than objects.
>
> -- Doug Hoyte
Функция в **JS** находится сразу в двух контекстах: *динамическом* (`this`, значения параметров) и *лексическом* (переменные из более высоких областей
видимости в **месте определения функции**). *Динамический контекст* - это контекст **вызова** функции - значение параметров и окружение на момент вызова;
*лексический контекст* - контекст **определения** функции, её вложенности в другие области видимости, доступ к которым функция имеет и после окончания
выполнения функций-владельцев этих областей видимости.
### [Let Over Lambda](https://letoverlambda.com/) (LOL)
```javascript
{
let randomValue = Math.random()
var getRandomValue = () => randomValue
}
alert(getRandomValue())
```
### makeAdder
```javascript
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
```javascript
function makeCounter(){
var counter = 0;
return function(){
return counter++;
}
}
```
Функция выше создает счетчик, значение которого можно узнать из возвращаемой анонимной функции. При этом счетчик увеличится на 1. Если расширить функционал
данного примера для чтения и декремента счетчика, получим, например:
```javascript
function makeCounter(){
var counter = 0;
function inc(){
return counter++;
}
function dec(){
return counter--;
}
function read(){
return counter;
}
return [inc,dec,read];
}
```
Результатом выполнения `makeCounter` будет *массив функций*. Однако намного более наглядным будет создание именованного массива, т. е. объекта:
```javascript
function makeCounter(){
var counter = 0;
function inc(){
return counter++;
}
function dec(){
return counter--;
}
function read(){
return counter;
}
return {inc: inc, dec: dec, read: read};
}
```
> **Задание**
> Сделайте счетчик кликов с помощью функции и замыкания. Счетчик должен выводить количество кликов в `innerText` элемента.
```javascript
clickCounter(document.getElementById('button')) //кнопка с id='button' при каждом клике показывает число кликов.
clickCounter(document.getElementById('span')) //span с id='span' при каждом клике показывает число кликов.
```
## ООП в функциональном стиле.
### Создание объекта
Для создания объектов используются функции-конструкторы. Они создают новые объекты определенного типа, который совпадает с именем функции:
```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(/*this*/){
return this.name + (this.fatherName ? " " + this.fatherName + " " : " ") + this.surname
}
}
var person = new Person("Ivan", "Petroff")
alert(person.getFullName())
person.fatherName = "Sydorych"
alert(person.getFullName())
```
`this` можно считать скрытым параметром функции, если функция **вызвана через точку** как поле объекта:
```javascript
alert(person.getFullName()) //в качестве this в getFullName передается person
```
### Приватные методы и данные; Замыкания.
**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()
```