update
This commit is contained in:
parent
962caebbb7
commit
87bf53d076
1825 changed files with 94929 additions and 0 deletions
|
@ -0,0 +1,15 @@
|
|||
Изменения в методе `run`:
|
||||
|
||||
```js
|
||||
this.run = function() {
|
||||
*!*
|
||||
if (!this._enabled) {
|
||||
throw new Error("Кофеварка выключена");
|
||||
}
|
||||
*/!*
|
||||
|
||||
setTimeout(onReady, 1000);
|
||||
};
|
||||
```
|
||||
|
||||
[edit src="solution" /]
|
|
@ -0,0 +1,48 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function Machine(power) {
|
||||
this._enabled = false;
|
||||
|
||||
this.enable = function() {
|
||||
this._enabled = true;
|
||||
};
|
||||
|
||||
this.disable = function() {
|
||||
this._enabled = false;
|
||||
};
|
||||
}
|
||||
|
||||
function CoffeeMachine(power) {
|
||||
Machine.apply(this, arguments);
|
||||
|
||||
var waterAmount = 0;
|
||||
|
||||
this.setWaterAmount = function(amount) {
|
||||
waterAmount = amount;
|
||||
};
|
||||
|
||||
function onReady() {
|
||||
alert('Кофе готов!');
|
||||
}
|
||||
|
||||
this.run = function() {
|
||||
if (!this._enabled) {
|
||||
throw new Error("Кофеварка выключена");
|
||||
}
|
||||
setTimeout(onReady, 1000);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
var coffeeMachine = new CoffeeMachine(10000);
|
||||
coffeeMachine.run();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,42 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function Machine(power) {
|
||||
this._enabled = false;
|
||||
|
||||
this.enable = function() {
|
||||
this._enabled = true;
|
||||
};
|
||||
|
||||
this.disable = function() {
|
||||
this._enabled = false;
|
||||
};
|
||||
}
|
||||
|
||||
function CoffeeMachine(power) {
|
||||
Machine.apply(this, arguments);
|
||||
|
||||
var waterAmount = 0;
|
||||
|
||||
this.setWaterAmount = function(amount) {
|
||||
waterAmount = amount;
|
||||
};
|
||||
|
||||
function onReady() {
|
||||
alert('Кофе готово!');
|
||||
}
|
||||
|
||||
this.run = function() {
|
||||
setTimeout(onReady, 1000);
|
||||
};
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,22 @@
|
|||
# Запускать только при включённой кофеварке
|
||||
|
||||
[importance 5]
|
||||
|
||||
В коде `CoffeeMachine` сделайте так, чтобы метод `run` выводил ошибку, если кофеварка выключена.
|
||||
|
||||
В итоге должен работать такой код:
|
||||
|
||||
```js
|
||||
var coffeeMachine = new CoffeeMachine(10000);
|
||||
coffeeMachine.run(); // ошибка, кофеварка выключена!
|
||||
```
|
||||
|
||||
А вот так -- всё в порядке:
|
||||
|
||||
```js
|
||||
var coffeeMachine = new CoffeeMachine(10000);
|
||||
coffeeMachine.enable();
|
||||
coffeeMachine.run(); // ...Кофе готов!
|
||||
```
|
||||
|
||||
[edit src="source" task /]
|
|
@ -0,0 +1 @@
|
|||
[edit src="solution"/]
|
|
@ -0,0 +1,55 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function Machine(power) {
|
||||
this._enabled = false;
|
||||
|
||||
this.enable = function() {
|
||||
this._enabled = true;
|
||||
};
|
||||
|
||||
this.disable = function() {
|
||||
this._enabled = false;
|
||||
};
|
||||
}
|
||||
|
||||
function CoffeeMachine(power) {
|
||||
Machine.apply(this, arguments);
|
||||
|
||||
var waterAmount = 0;
|
||||
var timerId;
|
||||
|
||||
this.setWaterAmount = function(amount) {
|
||||
waterAmount = amount;
|
||||
};
|
||||
|
||||
function onReady() {
|
||||
alert('Кофе готов!');
|
||||
}
|
||||
|
||||
var parentDisable = this.disable;
|
||||
this.disable = function() {
|
||||
parentDisable.call(this);
|
||||
clearTimeout(timerId);
|
||||
}
|
||||
|
||||
this.run = function() {
|
||||
if (!this._enabled) {
|
||||
throw new Error("Кофеварка выключена");
|
||||
}
|
||||
timerId = setTimeout(onReady, 1000);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
var coffeeMachine = new CoffeeMachine(10000);
|
||||
coffeeMachine.run();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,16 @@
|
|||
# Останавливать кофеварку при выключении
|
||||
|
||||
[importance 5]
|
||||
|
||||
Когда кофеварку выключают -- текущая варка кофе должна останавливаться.
|
||||
|
||||
Например, следующий код кофе не сварит:
|
||||
|
||||
```js
|
||||
var coffeeMachine = new CoffeeMachine(10000);
|
||||
coffeeMachine.enable();
|
||||
coffeeMachine.run();
|
||||
coffeeMachine.disable(); // остановит работу, ничего не выведет
|
||||
```
|
||||
|
||||
Реализуйте это на основе решения [предыдущей задачи](/task/coffeemachine-fix-run).
|
|
@ -0,0 +1,29 @@
|
|||
Решение:
|
||||
|
||||
```js
|
||||
function Fridge(power) {
|
||||
// унаследовать
|
||||
Machine.apply(this, arguments);
|
||||
|
||||
var food = []; // приватное свойство food
|
||||
|
||||
this.addFood = function() {
|
||||
if (!this._enabled) {
|
||||
throw new Error("Холодильник выключен");
|
||||
}
|
||||
if (food.length + arguments.length >= this._power / 100) {
|
||||
throw new Error("Нельзя добавить, не хватает мощности");
|
||||
}
|
||||
for(var i=0; i<arguments.length; i++) {
|
||||
food.push(arguments[i]); // добавить всё из arguments
|
||||
}
|
||||
};
|
||||
|
||||
this.getFood = function() {
|
||||
// копируем еду в новый массив, чтобы манипуляции с ним не меняли food
|
||||
return food.slice();
|
||||
};
|
||||
|
||||
}
|
||||
```
|
||||
|
67
1-js/8-oop/5-functional-inheritance/3-inherit-fridge/task.md
Normal file
67
1-js/8-oop/5-functional-inheritance/3-inherit-fridge/task.md
Normal file
|
@ -0,0 +1,67 @@
|
|||
# Унаследуйте холодильник
|
||||
|
||||
[importance 4]
|
||||
|
||||
Создайте класс для холодильника `Fridge(power)`, наследующий от `Machine`, с приватным свойством `food` и методами `addFood(...)`, `getFood()`:
|
||||
<ul>
|
||||
<li>Приватное свойство `food` хранит массив еды.</li>
|
||||
<li>Публичный метод `addFood(item)` добавляет в массив `food` новую еду, доступен вызов с несколькими аргументами `addFood(item1, item2...)` для добавления нескольких элементов сразу.</li>
|
||||
<li>Если холодильник выключен, то добавить еду нельзя, будет ошибка.</li>
|
||||
<li>Максимальное количество еды ограничено `power/100`, где `power` -- мощность холодильника, указывается в конструкторе. При попытке добавить больше -- будет ошибка</li>
|
||||
<li>Публичный метод `getFood()` возвращает еду в виде массива, добавление или удаление элементов из которого не должно влиять на свойство `food` холодильника.</li>
|
||||
</ul>
|
||||
|
||||
Код для проверки:
|
||||
|
||||
```js
|
||||
var fridge = new Fridge(200);
|
||||
fridge.addFood("котлета"); // ошибка, холодильник выключен
|
||||
```
|
||||
|
||||
Ещё код для проверки:
|
||||
|
||||
```js
|
||||
// создать холодильник мощностью 500 (не более 5 еды)
|
||||
var fridge = new Fridge(500);
|
||||
fridge.enable();
|
||||
fridge.addFood("котлета");
|
||||
fridge.addFood("сок", "зелень");
|
||||
fridge.addFood("варенье", "пирог", "торт"); // ошибка, слишком много еды
|
||||
```
|
||||
|
||||
Код использования холодильника без ошибок:
|
||||
|
||||
```js
|
||||
var fridge = new Fridge(500);
|
||||
fridge.enable();
|
||||
fridge.addFood("котлета");
|
||||
fridge.addFood("сок", "варенье");
|
||||
|
||||
var fridgeFood = fridge.getFood();
|
||||
alert(fridgeFood); // котлета, сок, варенье
|
||||
|
||||
// добавление элементов не влияет на еду в холодильнике
|
||||
fridgeFood.push("вилка", "ложка");
|
||||
|
||||
alert(fridge.getFood()); // внутри по-прежнему: котлета, сок, варенье
|
||||
```
|
||||
|
||||
Исходный код класса `Machine`, от которого нужно наследовать:
|
||||
|
||||
```js
|
||||
function Machine(power) {
|
||||
this._power = power;
|
||||
this._enabled = false;
|
||||
|
||||
var self = this;
|
||||
|
||||
this.enable = function() {
|
||||
self._enabled = true;
|
||||
};
|
||||
|
||||
this.disable = function() {
|
||||
self._enabled = false;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function Machine(power) {
|
||||
this._power = power;
|
||||
this._enabled = false;
|
||||
|
||||
var self = this;
|
||||
|
||||
this.enable = function() {
|
||||
self._enabled = true;
|
||||
};
|
||||
|
||||
this.disable = function() {
|
||||
self._enabled = false;
|
||||
};
|
||||
}
|
||||
|
||||
function Fridge(power) {
|
||||
// унаследовать
|
||||
Machine.apply(this, arguments);
|
||||
|
||||
var food = []; // приватное свойство food
|
||||
|
||||
this.addFood = function() {
|
||||
if (!this._enabled) {
|
||||
throw new Error("Холодильник выключен");
|
||||
}
|
||||
if (food.length + arguments.length >= this._power / 100) {
|
||||
throw new Error("Нельзя добавить, не хватает мощности");
|
||||
}
|
||||
for(var i=0; i<arguments.length; i++) {
|
||||
food.push(arguments[i]); // добавить всё из arguments
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
this.getFood = function() {
|
||||
// копируем еду в новый массив, чтобы манипуляции с ним не меняли food
|
||||
return food.slice();
|
||||
};
|
||||
|
||||
*!*
|
||||
this.filterFood = function(filter) {
|
||||
return food.filter(filter);
|
||||
};
|
||||
|
||||
this.removeFood = function(item) {
|
||||
var idx = food.indexOf(item);
|
||||
if (idx != -1) food.splice(idx, 1);
|
||||
};
|
||||
*/!*
|
||||
}
|
||||
|
||||
var fridge = new Fridge(500);
|
||||
fridge.enable();
|
||||
fridge.addFood({ title: "котлета", calories: 100 });
|
||||
fridge.addFood({ title: "сок", calories: 30 });
|
||||
fridge.addFood({ title: "зелень", calories: 10 });
|
||||
fridge.addFood({ title: "варенье", calories: 150 });
|
||||
|
||||
var dietItems = fridge.filterFood(function(item) {
|
||||
return item.calories < 50;
|
||||
});
|
||||
|
||||
fridge.removeFood("нет такой еды"); // без эффекта
|
||||
alert(fridge.getFood().length); // 4
|
||||
|
||||
dietItems.forEach(function(item) {
|
||||
alert(item.title); // сок, зелень
|
||||
fridge.removeFood(item);
|
||||
});
|
||||
|
||||
alert(fridge.getFood().length); // 2
|
||||
```
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
# Добавьте методы в холодильник
|
||||
|
||||
[importance 5]
|
||||
|
||||
Добавьте в холодильник методы:
|
||||
<ul>
|
||||
<li>Публичный метод `filterFood(func)`, который возвращает всю еду, для которой `func(item) == true`</li>
|
||||
<li>Публичный метод `removeFood(item)`, который удаляет еду `item` из холодильника.</li>
|
||||
</ul>
|
||||
|
||||
Код для проверки:
|
||||
|
||||
```js
|
||||
var fridge = new Fridge(500);
|
||||
fridge.enable();
|
||||
fridge.addFood({ title: "котлета", calories: 100 });
|
||||
fridge.addFood({ title: "сок", calories: 30 });
|
||||
fridge.addFood({ title: "зелень", calories: 10 });
|
||||
fridge.addFood({ title: "варенье", calories: 150 });
|
||||
|
||||
fridge.removeFood("нет такой еды"); // без эффекта
|
||||
alert(fridge.getFood().length); // 4
|
||||
|
||||
var dietItems = fridge.filterFood(function(item) {
|
||||
return item.calories < 50;
|
||||
});
|
||||
|
||||
dietItems.forEach(function(item) {
|
||||
alert(item.title); // сок, зелень
|
||||
fridge.removeFood(item);
|
||||
});
|
||||
|
||||
alert(fridge.getFood().length); // 2
|
||||
```
|
||||
|
||||
В качестве исходного кода используйте решение [предыдущей задачи](/task/inherit-fridge).
|
|
@ -0,0 +1,68 @@
|
|||
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function Machine(power) {
|
||||
this._power = power;
|
||||
this._enabled = false;
|
||||
|
||||
var self = this;
|
||||
|
||||
this.enable = function() {
|
||||
self._enabled = true;
|
||||
};
|
||||
|
||||
this.disable = function() {
|
||||
self._enabled = false;
|
||||
};
|
||||
}
|
||||
|
||||
function Fridge(power) {
|
||||
Machine.apply(this, arguments);
|
||||
|
||||
var food = []; // приватное свойство food
|
||||
|
||||
this.addFood = function() {
|
||||
if (!this._enabled) {
|
||||
throw new Error("Холодильник выключен");
|
||||
}
|
||||
if (food.length + arguments.length >= this._power / 100) {
|
||||
throw new Error("Нельзя добавить, не хватает мощности");
|
||||
}
|
||||
for(var i=0; i<arguments.length; i++) {
|
||||
food.push(arguments[i]); // добавить всё из arguments
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
this.getFood = function() {
|
||||
// копируем еду в новый массив, чтобы манипуляции с ним не меняли food
|
||||
return food.slice();
|
||||
};
|
||||
|
||||
this.filterFood = function(filter) {
|
||||
return food.filter(filter);
|
||||
};
|
||||
|
||||
this.removeFood = function(item) {
|
||||
var idx = food.indexOf(item);
|
||||
if (idx != -1) food.splice(idx, 1);
|
||||
};
|
||||
|
||||
*!*
|
||||
var parentDisable = this.disable;
|
||||
this.disable = function() {
|
||||
if (food.length) {
|
||||
throw new Error("Нельзя выключить: внутри еда");
|
||||
}
|
||||
parentDisable();
|
||||
};
|
||||
*/!*
|
||||
}
|
||||
|
||||
var fridge = new Fridge(500);
|
||||
fridge.enable();
|
||||
fridge.addFood("кус-кус");
|
||||
fridge.disable(); // ошибка, в холодильнике есть еда
|
||||
```
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# Переопределите disable
|
||||
|
||||
[importance 5]
|
||||
|
||||
Переопределите метод `disable` холодильника, чтобы при наличии в нём еды он выдавал ошибку.
|
||||
|
||||
|
||||
Код для проверки:
|
||||
|
||||
```js
|
||||
var fridge = new Fridge(500);
|
||||
fridge.enable();
|
||||
fridge.addFood("кус-кус");
|
||||
fridge.disable(); // ошибка, в холодильнике есть еда
|
||||
```
|
||||
|
||||
В качестве исходного кода используйте решение [предыдущей задачи](/task/add-methods-fridge).
|
401
1-js/8-oop/5-functional-inheritance/article.md
Normal file
401
1-js/8-oop/5-functional-inheritance/article.md
Normal file
|
@ -0,0 +1,401 @@
|
|||
# Функциональное наследование
|
||||
|
||||
Наследование -- это создание новых "классов" на основе существующих.
|
||||
|
||||
В JavaScript его можно реализовать несколькими путями, один из которых -- с использованием наложения конструкторов, мы рассмотрим в этой главе.
|
||||
[cut]
|
||||
|
||||
## Зачем наследование?
|
||||
|
||||
Ранее мы обсуждали различные реализации кофеварки. Продолжим эту тему далее.
|
||||
|
||||
Хватит ли нам только кофеварки для удобной жизни? Вряд ли... Скорее всего, ещё понадобятся как минимум холодильник, микроволновка, а возможно и другие *машины*.
|
||||
|
||||
В реальной жизни у этих *машин* есть базовые правила пользования. Например, большая кнопка <i class="fa fa-power-off"></i> -- включение, шнур с розеткой нужно воткнуть в питание и т.п.
|
||||
|
||||
Можно сказать, что "у всех машин есть общие свойства, а конкретные машины могут их дополнять".
|
||||
|
||||
Именно поэтому, увидев новую технику, мы уже можем что-то с ней сделать, даже не читая инструкцию.
|
||||
|
||||
**Механизм наследования позволяет определить базовый класс `Машина`, в нём описать то, что свойственно всем машинам, а затем на его основе построить другие, более конкретные: `Кофеварка`, `Холодильник` и т.п.**
|
||||
|
||||
[smart header="В веб-разработке всё так же"]
|
||||
В веб-разработке нам могут понадобиться классы `Меню`, `Табы`, `Диалог` и другие компоненты интерфейса.
|
||||
|
||||
Можно выделить полезный общий функционал в класс `Компонент` и наследовать их от него, чтобы не дублировать код. Это обычная практика, принятая во множестве библиотек.
|
||||
[/smart]
|
||||
|
||||
## Наследование от Machine
|
||||
|
||||
Например, у нас есть класс `Machine`, который реализует методы "включить" `enable()` и "выключить" `disable()`:
|
||||
|
||||
```js
|
||||
function Machine() {
|
||||
var enabled = false;
|
||||
|
||||
this.enable = function() {
|
||||
enabled = true;
|
||||
};
|
||||
|
||||
this.disable = function() {
|
||||
enabled = false;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Унаследуем от него кофеварку. При этом она получит эти методы автоматически:
|
||||
|
||||
```js
|
||||
function CoffeeMachine(power) {
|
||||
*!*
|
||||
Machine.call(this);
|
||||
*/!*
|
||||
var waterAmount = 0;
|
||||
|
||||
this.setWaterAmount = function(amount) {
|
||||
waterAmount = amount;
|
||||
};
|
||||
|
||||
function onReady() {
|
||||
alert('Кофе готово!');
|
||||
}
|
||||
|
||||
this.run = function() {
|
||||
setTimeout(onReady, 1000);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
var coffeeMachine = new CoffeeMachine(10000);
|
||||
coffeeMachine.enable();
|
||||
```
|
||||
|
||||
Наследование реализовано вызовом `Machine.call(this)` в начале `CoffeeMachine`.
|
||||
|
||||
Он вызывает функцию `Machine`, передавая ей в качестве контекста `this` текущий объект. `Machine`, в процессе выполнения, записывает в `this` различные полезные свойства и методы, в нашем случае `this.enable` и `this.disable`.
|
||||
|
||||
Далее `CoffeeMachine` продолжает выполнение и может добавить свои свойства и методы, а также пользоваться унаследованными.
|
||||
|
||||
## Защищённые свойства
|
||||
|
||||
В коде выше есть одна проблема.
|
||||
|
||||
**Наследник не имеет доступа к приватным свойствам родителя.**
|
||||
|
||||
Иначе говоря, если кофеварка захочет обратиться к `enabled`, то её ждёт разочарование:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function Machine() {
|
||||
var enabled = false;
|
||||
|
||||
this.enable = function() {
|
||||
enabled = true;
|
||||
};
|
||||
|
||||
this.disable = function() {
|
||||
enabled = false;
|
||||
};
|
||||
}
|
||||
|
||||
function CoffeeMachine(power) {
|
||||
Machine.call(this);
|
||||
|
||||
this.enable();
|
||||
|
||||
*!*
|
||||
// ошибка, переменная не определена!
|
||||
alert(enabled);
|
||||
*/!*
|
||||
}
|
||||
|
||||
var coffeeMachine = new CoffeeMachine(10000);
|
||||
```
|
||||
|
||||
Это естественно, ведь `enabled` -- локальная переменная функции `Machine`. Она находится в другой области видимости.
|
||||
|
||||
**Чтобы наследник имел доступ к свойству, оно должно быть записано в `this`.**
|
||||
|
||||
**При этом, чтобы обозначить, что свойство является внутренним, его имя начинают с подчёркивания `_`.**
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function Machine() {
|
||||
*!*
|
||||
this._enabled = false;
|
||||
*/!*
|
||||
|
||||
this.enable = function() {
|
||||
this._enabled = true;
|
||||
};
|
||||
|
||||
this.disable = function() {
|
||||
this._enabled = false;
|
||||
};
|
||||
}
|
||||
|
||||
function CoffeeMachine(power) {
|
||||
Machine.call(this);
|
||||
|
||||
this.enable();
|
||||
|
||||
*!*
|
||||
alert(this._enabled); // true
|
||||
*/!*
|
||||
}
|
||||
|
||||
var coffeeMachine = new CoffeeMachine(10000);
|
||||
```
|
||||
|
||||
**Подчёркивание в начале свойства -- общепринятый знак, что свойство является внутренним, предназначенным лишь для доступа из самого объекта и его наследников. Такие свойства называют *защищёнными*.**
|
||||
|
||||
Технически это, конечно, возможно, но приличный программист снаружи в такое свойство не полезет.
|
||||
|
||||
**Вообще, это стандартная практика: конструктор сохраняет свои параметры в свойствах объекта. Иначе наследники не будут иметь к ним доступ.**
|
||||
|
||||
## Перенос свойства в защищённые
|
||||
|
||||
В коде выше есть свойство `power`. Сейчас мы его тоже сделаем защищённым и перенесём в `Machine`, поскольку "мощность" свойственна всем машинам, а не только кофеварке:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function CoffeeMachine(power) {
|
||||
*!*
|
||||
Machine.apply(this, arguments); // (1)
|
||||
*/!*
|
||||
|
||||
alert(this._enabled); // false
|
||||
alert(this._power); // 10000
|
||||
}
|
||||
|
||||
function Machine(power) {
|
||||
*!*
|
||||
this._power = power; // (2)
|
||||
*/!*
|
||||
|
||||
this._enabled = false;
|
||||
|
||||
this.enable = function() {
|
||||
this._enabled = true;
|
||||
};
|
||||
|
||||
this.disable = function() {
|
||||
this._enabled = false;
|
||||
};
|
||||
}
|
||||
|
||||
var coffeeMachine = new CoffeeMachine(10000);
|
||||
```
|
||||
|
||||
В коде выше при вызове `new CoffeeMachine(10000)` в строке `(1)` кофеварка передаёт аргументы и контекст родителю вызовом `Machine.apply(this, arguments)`.
|
||||
|
||||
Можно было бы использовать `Machine.call(this, power)`, но использование `apply` гарантирует передачу всех аргументов, мало ли, вдруг мы в будущем захотим их добавить.
|
||||
|
||||
Далее конструктор `Machine` в строке `(2)` сохраняет `power` в свойстве объекта `this._power`, благодаря этому кофеварка, когда наследование перейдёт обратно к `CoffeeMachine`, сможет сразу обращаться к нему.
|
||||
|
||||
## Переопределение методов
|
||||
|
||||
Итак, мы получили класс `CoffeeMachine`, который наследует от `Machine`.
|
||||
|
||||
Аналогичным образом мы можем унаследовать от `Machine` холодильник `Fridge`, микроволновку `MicroOven` и другие классы, которые разделяют общий "машинный" функционал.
|
||||
|
||||
Для этого достаточно вызвать `Machine` текущем контексте, а затем добавить свои методы.
|
||||
|
||||
```js
|
||||
// Fridge может добавить и свои аргументы,
|
||||
// которые в Machine не будут использованы
|
||||
function Fridge(power, temperature) {
|
||||
Machine.call(this, arguments);
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Кроме создания новых методов, можно заменить унаследованные на свои:
|
||||
|
||||
```js
|
||||
function CoffeeMachine(power, capacity) {
|
||||
Machine.apply(this, arguments);
|
||||
|
||||
// переопределить this.enable
|
||||
this.enable = function() {
|
||||
/* enable для кофеварки */
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
...Однако, как правило, мы хотим не заменить, а *расширить* метод родителя. Например, сделать так, чтобы при включении кофеварка тут же запускалась.
|
||||
|
||||
Для этого метод родителя предварительно копируют в переменную, и затем вызывают внутри нового `enable` -- там, где считают нужным:
|
||||
|
||||
```js
|
||||
function CoffeeMachine(power) {
|
||||
Machine.apply(this, arguments);
|
||||
|
||||
*!*
|
||||
var parentEnable = this.enable; // (1)
|
||||
this.enable = function() { // (2)
|
||||
parentEnable.call(this); // (3)
|
||||
this.run(); // (4)
|
||||
}
|
||||
*/!*
|
||||
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**Общая схема переопределения метода (по строкам выделенного фрагмента кода):**
|
||||
|
||||
<ol>
|
||||
<li>Мы скопировали доставшийся от родителя метод `enable` в переменную, например `parentEnable`.</li>
|
||||
<li>Заменили метод `this.enable()` на свою функцию...</li>
|
||||
<li>Которая по-прежнему реализует старый функционал через вызов `parentEnable`...</li>
|
||||
<li>И в дополнение к нему делает что-то своё, например запускает приготовление кофе.</li>
|
||||
</ol>
|
||||
|
||||
Обратим внимание на строку `(3)`.
|
||||
|
||||
В ней родительский метод вызывается так: `parentEnable.call(this)`. Если бы вызов был таким: `parentEnable()`, то ему бы не передался текущий `this` и возникла бы ошибка.
|
||||
|
||||
Технически, можно сделать возможность вызывать его и как `parentEnable()`, но тогда надо гарантировать, что контекст будет правильным, например привязать его при помощи `bind` или при объявлении, в родителе, вообще не использовать `this`, а получать контекст через замыкание, вот так:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function Machine(power) {
|
||||
this._enabled = false;
|
||||
|
||||
*!*
|
||||
var self = this;
|
||||
|
||||
this.enable = function() {
|
||||
// используем внешнюю переменную вместо this
|
||||
self._enabled = true;
|
||||
};
|
||||
*/!*
|
||||
|
||||
this.disable = function() {
|
||||
this._enabled = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
function CoffeeMachine(power) {
|
||||
Machine.apply(this, arguments);
|
||||
|
||||
var waterAmount = 0;
|
||||
|
||||
this.setWaterAmount = function(amount) {
|
||||
waterAmount = amount;
|
||||
};
|
||||
|
||||
*!*
|
||||
var parentEnable = this.enable;
|
||||
this.enable = function() {
|
||||
parentEnable(); // теперь можно вызывать как угодно, this не важен
|
||||
this.run();
|
||||
}
|
||||
*/!*
|
||||
|
||||
function onReady() {
|
||||
alert('Кофе готово!');
|
||||
}
|
||||
|
||||
this.run = function() {
|
||||
setTimeout(onReady, 1000);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
var coffeeMachine = new CoffeeMachine(10000);
|
||||
coffeeMachine.setWaterAmount(50);
|
||||
coffeeMachine.enable();
|
||||
```
|
||||
|
||||
В коде выше родительский метод `parentEnable = this.enable` успешно продолжает работать даже при вызове без контекста. А всё потому, что использует `self` внутри.
|
||||
|
||||
## Итого
|
||||
|
||||
Организация наследования, которая описана в этой главе, называется "функциональным паттерном наследования".
|
||||
|
||||
Её общая схема (кратко):
|
||||
|
||||
<ol>
|
||||
<li>Объявляется конструктор родителя `Machine`. В нём могут быть приватные (private), публичные (public) и защищённые (protected) свойства:
|
||||
|
||||
```js
|
||||
function Machine(params) {
|
||||
// локальные переменные и функции доступны только внутри Machine
|
||||
var private;
|
||||
|
||||
// публичные доступны снаружи
|
||||
this.public = ...;
|
||||
|
||||
// защищённые доступны внутри Machine и для потомков
|
||||
// мы договариваемся не трогать их снаружи
|
||||
this._protected = ...
|
||||
}
|
||||
|
||||
var machine = new Machine(...)
|
||||
machine.public();
|
||||
```
|
||||
|
||||
</li>
|
||||
<li>Для наследования конструктор потомка вызывает родителя в своём контексте через `apply`. После чего может добавить свои переменные и методы:
|
||||
|
||||
```js
|
||||
function CoffeeMachine(params) {
|
||||
// универсальный вызов с передачей любых аргументов
|
||||
*!*
|
||||
Machine.apply(this, arguments);
|
||||
*/!*
|
||||
|
||||
this.coffeePublic = ...
|
||||
}
|
||||
|
||||
var coffeeMachine = new CoffeeMachine(...);
|
||||
coffeeMachine.public();
|
||||
coffeeMachine.coffeePublic();
|
||||
```
|
||||
|
||||
</li>
|
||||
<li>В `CoffeeMachine` свойства, полученные от родителя, можно перезаписать своими. Но обычно требуется не заменить, а расширить метод родителя. Для этого он предварительно копируется в переменную:
|
||||
|
||||
```js
|
||||
function CoffeeMachine(params) {
|
||||
Machine.apply(this, arguments);
|
||||
|
||||
*!*
|
||||
var parentProtected = this._protected;
|
||||
this._protected = function(args) {
|
||||
parentProtected.call(this, args); // (*)
|
||||
// ...
|
||||
};
|
||||
*/!*
|
||||
}
|
||||
```
|
||||
|
||||
Строку `(*)` можно упростить до `parentProtected(args)`, если метод родителя не использует `this`, а, например, привязан к `var self = this`:
|
||||
|
||||
```js
|
||||
function Machine(params) {
|
||||
var self = this;
|
||||
|
||||
this._protected = function() {
|
||||
self.property = "value";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
**В следующих главах мы будем изучать прототипный подход, который обладаем рядом преимуществ, по сравнению с функциональным.**
|
||||
|
||||
Но функциональный тоже бывает полезен.
|
||||
|
||||
В своей практике разработки я обычно наследую функционально в тех случаях, когда *уже* есть какой-то код, который на нём построен. К примеру, уже существуют классы, написанные сторонними разработчиками, которые можно доопределить или расширить только так.
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue