|
@@ -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()
|
|
|
+```
|