Ivan Asmer 7 년 전
부모
커밋
576eb62671
1개의 변경된 파일426개의 추가작업 그리고 2개의 파일을 삭제
  1. 426 2
      A/06DOMClosuresOOP.md

+ 426 - 2
A/06DOMClosuresOOP.md

@@ -1,4 +1,110 @@
-# DOM, замыкания и функциональное ООП.
+# Рекурсия. 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**.
 
@@ -192,8 +298,326 @@ function makeCounter(){
 }
 ```
 
+
 > **Задание**
-> Используя событие `DOMContentLoaded` назначьте обработчик на клик на том или ином элементе, который будет считать и выводить в элемент **DOM** количество кликов.
+> Сделайте счетчик кликов с помощью функции и замыкания. Счетчик должен выводить количество кликов в `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()
+```