|
@@ -0,0 +1,215 @@
|
|
|
+# ООП функциональное. Замыкания
|
|
|
+
|
|
|
+## `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()
|
|
|
+```
|
|
|
+
|
|
|
+## Замыкания, приватные методы и данные.
|
|
|
+
|
|
|
+Функция в **JS** находится сразу в двух контекстах: *динамическом* (`this`, значения параметров) и *лексическом* (переменные из более высоких областей
|
|
|
+видимости в **месте определения функции**). *Динамический контекст* - это контекст **вызова** функции - значение параметров и окружение на момент вызова;
|
|
|
+*лексический контекст* - контекст **определения** функции, её вложенности в другие области видимости, доступ к которым функция имеет и после окончания
|
|
|
+выполнения функций-владельцев этих областей видимости.
|
|
|
+
|
|
|
+### 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**.
|
|
|
+
|
|
|
+```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];
|
|
|
+}
|
|
|
+```
|
|
|
+и получим *массив* функций. Однако намного более наглядным будет создания именованного массива, т. е. объекта:
|
|
|
+
|
|
|
+```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};
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+... что во многом схоже с обычным объектом сделанным через **функцию-конструктор**:
|
|
|
+
|
|
|
+```javascript
|
|
|
+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) !
|
|
|
+
|
|
|
+
|
|
|
+```javascript
|
|
|
+function factorial(N){
|
|
|
+ if (N <= 1){
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ return N * factorial(N -1);
|
|
|
+}
|
|
|
+
|
|
|
+factorial ( 5 ); // это 5 * factorial(4), что, в свою очередь, будет 4 * factorial(3) и так далее
|
|
|
+```
|
|
|
+
|
|
|
+При отладке рекурсий обращайте внимания на вложенность вызовов (Call Stack) и областей видимостей (Scopes).
|