up
|
@ -0,0 +1,43 @@
|
|||
Ошибка в строке:
|
||||
|
||||
```js
|
||||
Rabbit.prototype = Animal.prototype;
|
||||
```
|
||||
|
||||
Эта ошибка приведёт к тому, что `Rabbit.prototype` и `Animal.prototype` -- один и тот же объект. В результате методы `Rabbit` будут помещены в него и, при совпадении, перезапишут методы `Animal`.
|
||||
|
||||
Получится, что все животные прыгают, вот пример:
|
||||
|
||||
```js run no-beautify
|
||||
function Animal(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
Animal.prototype.walk = function() {
|
||||
alert("ходит " + this.name);
|
||||
};
|
||||
|
||||
function Rabbit(name) {
|
||||
this.name = name;
|
||||
}
|
||||
*!*
|
||||
Rabbit.prototype = Animal.prototype;
|
||||
*/!*
|
||||
|
||||
Rabbit.prototype.walk = function() {
|
||||
alert("прыгает! и ходит: " + this.name);
|
||||
};
|
||||
|
||||
*!*
|
||||
var animal = new Animal("Хрюшка");
|
||||
animal.walk(); // прыгает! и ходит Хрюшка
|
||||
*/!*
|
||||
```
|
||||
|
||||
Правильный вариант этой строки:
|
||||
|
||||
```js
|
||||
Rabbit.prototype = Object.create(Animal.prototype);
|
||||
```
|
||||
|
||||
Если так написать, то в `Rabbit.prototype` будет отдельный объект, который прототипно наследует от `Animal.prototype`, но может содержать и свои свойства, специфичные для кроликов.
|
|
@ -0,0 +1,27 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Найдите ошибку в наследовании
|
||||
|
||||
Найдите ошибку в прототипном наследовании. К чему она приведёт?
|
||||
|
||||
```js
|
||||
function Animal(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
Animal.prototype.walk = function() {
|
||||
alert( "ходит " + this.name );
|
||||
};
|
||||
|
||||
function Rabbit(name) {
|
||||
this.name = name;
|
||||
}
|
||||
Rabbit.prototype = Animal.prototype;
|
||||
|
||||
Rabbit.prototype.walk = function() {
|
||||
alert( "прыгает! и ходит: " + this.name );
|
||||
};
|
||||
```
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
Ошибка -- в том, что метод `walk` присваивается в конструкторе `Animal` самому объекту вместо прототипа.
|
||||
|
||||
Поэтому, если мы решим перезаписать этот метод своим, специфичным для кролика, то он не сработает:
|
||||
|
||||
```js
|
||||
// ...
|
||||
|
||||
// записывается в прототип
|
||||
Rabbit.prototype.walk = function() {
|
||||
alert( "прыгает " + this.name );
|
||||
};
|
||||
```
|
||||
|
||||
Метод `this.walk` из `Animal` записывается в сам объект, и поэтому он всегда будет первым, игнорируя цепочку прототипов.
|
||||
|
||||
Правильно было бы определять `walk` как `Animal.prototype.walk`.
|
||||
|
||||
Тем более, что этот метод является общим для всех объектов, тратить память и время на запись его в каждый конструктор определённо ни к чему.
|
|
@ -0,0 +1,31 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# В чём ошибка в наследовании
|
||||
|
||||
Найдите ошибку в прототипном наследовании. К чему она приведёт?
|
||||
|
||||
```js run
|
||||
function Animal(name) {
|
||||
this.name = name;
|
||||
|
||||
this.walk = function() {
|
||||
alert( "ходит " + this.name );
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
function Rabbit(name) {
|
||||
Animal.apply(this, arguments);
|
||||
}
|
||||
Rabbit.prototype = Object.create(Animal.prototype);
|
||||
|
||||
Rabbit.prototype.walk = function() {
|
||||
alert( "прыгает " + this.name );
|
||||
};
|
||||
|
||||
var rabbit = new Rabbit("Кроль");
|
||||
rabbit.walk();
|
||||
```
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
function Clock(options) {
|
||||
this._template = options.template;
|
||||
}
|
||||
|
||||
Clock.prototype._render = function render() {
|
||||
var date = new Date();
|
||||
|
||||
var hours = date.getHours();
|
||||
if (hours < 10) hours = '0' + hours;
|
||||
|
||||
var min = date.getMinutes();
|
||||
if (min < 10) min = '0' + min;
|
||||
|
||||
var sec = date.getSeconds();
|
||||
if (sec < 10) sec = '0' + sec;
|
||||
|
||||
var output = this._template.replace('h', hours).replace('m', min).replace('s', sec);
|
||||
|
||||
console.log(output);
|
||||
};
|
||||
|
||||
Clock.prototype.stop = function() {
|
||||
clearInterval(this._timer);
|
||||
};
|
||||
|
||||
Clock.prototype.start = function() {
|
||||
this._render();
|
||||
var self = this;
|
||||
this._timer = setInterval(function() {
|
||||
self._render();
|
||||
}, 1000);
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
|
||||
[js src="clock.js"]
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
function Clock(options) {
|
||||
this._template = options.template;
|
||||
}
|
||||
|
||||
Clock.prototype._render = function render() {
|
||||
var date = new Date();
|
||||
|
||||
var hours = date.getHours();
|
||||
if (hours < 10) hours = '0' + hours;
|
||||
|
||||
var min = date.getMinutes();
|
||||
if (min < 10) min = '0' + min;
|
||||
|
||||
var sec = date.getSeconds();
|
||||
if (sec < 10) sec = '0' + sec;
|
||||
|
||||
var output = this._template.replace('h', hours).replace('m', min).replace('s', sec);
|
||||
|
||||
console.log(output);
|
||||
};
|
||||
|
||||
Clock.prototype.stop = function() {
|
||||
clearInterval(this._timer);
|
||||
};
|
||||
|
||||
Clock.prototype.start = function() {
|
||||
this._render();
|
||||
var self = this;
|
||||
this._timer = setInterval(function() {
|
||||
self._render();
|
||||
}, 1000);
|
||||
};
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Часики в консоли</title>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script src="clock.js"></script>
|
||||
<script>
|
||||
var clock = new Clock({
|
||||
template: 'h:m:s'
|
||||
});
|
||||
clock.start();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,32 @@
|
|||
function Clock(options) {
|
||||
|
||||
var template = options.template;
|
||||
var timer;
|
||||
|
||||
function render() {
|
||||
var date = new Date();
|
||||
|
||||
var hours = date.getHours();
|
||||
if (hours < 10) hours = '0' + hours;
|
||||
|
||||
var min = date.getMinutes();
|
||||
if (min < 10) min = '0' + min;
|
||||
|
||||
var sec = date.getSeconds();
|
||||
if (sec < 10) sec = '0' + sec;
|
||||
|
||||
var output = template.replace('h', hours).replace('m', min).replace('s', sec);
|
||||
|
||||
console.log(output);
|
||||
}
|
||||
|
||||
this.stop = function() {
|
||||
clearInterval(timer);
|
||||
};
|
||||
|
||||
this.start = function() {
|
||||
render();
|
||||
timer = setInterval(render, 1000);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Часики в консоли</title>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script src="clock.js"></script>
|
||||
<script>
|
||||
var clock = new Clock({
|
||||
template: 'h:m:s'
|
||||
});
|
||||
clock.start();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,11 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Класс "часы"
|
||||
|
||||
Есть реализация часиков, оформленная в виде одной функции-конструктора. У неё есть приватные свойства `timer`, `template` и метод `render`.
|
||||
|
||||
Задача: переписать часы на прототипах. Приватные свойства и методы сделать защищёнными.
|
||||
|
||||
P.S. Часики тикают в браузерной консоли (надо открыть её, чтобы увидеть).
|
|
@ -0,0 +1,14 @@
|
|||
function ExtendedClock(options) {
|
||||
Clock.apply(this, arguments);
|
||||
this._precision = +options.precision || 1000;
|
||||
}
|
||||
|
||||
ExtendedClock.prototype = Object.create(Clock.prototype);
|
||||
|
||||
ExtendedClock.prototype.start = function() {
|
||||
this._render();
|
||||
var self = this;
|
||||
this._timer = setInterval(function() {
|
||||
self._render();
|
||||
}, this._precision);
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
Наследник:
|
||||
|
||||
[js src="extended-clock.js"]
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
function Clock(options) {
|
||||
this._template = options.template;
|
||||
}
|
||||
|
||||
Clock.prototype._render = function render() {
|
||||
var date = new Date();
|
||||
|
||||
var hours = date.getHours();
|
||||
if (hours < 10) hours = '0' + hours;
|
||||
|
||||
var min = date.getMinutes();
|
||||
if (min < 10) min = '0' + min;
|
||||
|
||||
var sec = date.getSeconds();
|
||||
if (sec < 10) sec = '0' + sec;
|
||||
|
||||
var output = this._template.replace('h', hours).replace('m', min).replace('s', sec);
|
||||
|
||||
console.log(output);
|
||||
};
|
||||
|
||||
Clock.prototype.stop = function() {
|
||||
clearInterval(this._timer);
|
||||
};
|
||||
|
||||
Clock.prototype.start = function() {
|
||||
this._render();
|
||||
var self = this;
|
||||
this._timer = setInterval(function() {
|
||||
self._render();
|
||||
}, 1000);
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
function ExtendedClock(options) {
|
||||
Clock.apply(this, arguments);
|
||||
this._precision = +options.precision || 1000;
|
||||
}
|
||||
|
||||
ExtendedClock.prototype = Object.create(Clock.prototype);
|
||||
|
||||
ExtendedClock.prototype.start = function() {
|
||||
this._render();
|
||||
var self = this;
|
||||
this._timer = setInterval(function() {
|
||||
self._render();
|
||||
}, this._precision);
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Часики в консоли</title>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script src="clock.js"></script>
|
||||
|
||||
<script src="extended-clock.js"></script>
|
||||
|
||||
<script>
|
||||
var lowResolutionClock = new ExtendedClock({
|
||||
template: 'h:m:s',
|
||||
precision: 10000
|
||||
});
|
||||
|
||||
lowResolutionClock.start();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,32 @@
|
|||
function Clock(options) {
|
||||
this._template = options.template;
|
||||
}
|
||||
|
||||
Clock.prototype._render = function render() {
|
||||
var date = new Date();
|
||||
|
||||
var hours = date.getHours();
|
||||
if (hours < 10) hours = '0' + hours;
|
||||
|
||||
var min = date.getMinutes();
|
||||
if (min < 10) min = '0' + min;
|
||||
|
||||
var sec = date.getSeconds();
|
||||
if (sec < 10) sec = '0' + sec;
|
||||
|
||||
var output = this._template.replace('h', hours).replace('m', min).replace('s', sec);
|
||||
|
||||
console.log(output);
|
||||
};
|
||||
|
||||
Clock.prototype.stop = function() {
|
||||
clearInterval(this._timer);
|
||||
};
|
||||
|
||||
Clock.prototype.start = function() {
|
||||
this._render();
|
||||
var self = this;
|
||||
this._timer = setInterval(function() {
|
||||
self._render();
|
||||
}, 1000);
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
function extend(Child, Parent) {
|
||||
Child.prototype = inherit(Parent.prototype);
|
||||
Child.prototype.constructor = Child;
|
||||
Child.parent = Parent.prototype;
|
||||
}
|
||||
|
||||
function inherit(proto) {
|
||||
function F() {}
|
||||
F.prototype = proto;
|
||||
return new F;
|
||||
}
|
||||
|
||||
// ваш код
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Часики в консоли</title>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- исходные часы, от них нужно унаследовать -->
|
||||
<script src="clock.js"></script>
|
||||
<script>
|
||||
var clock = new Clock({
|
||||
template: 'h:m:s'
|
||||
});
|
||||
clock.start();
|
||||
|
||||
|
||||
/* ... ваш код для ExtendedClock */
|
||||
|
||||
/*
|
||||
Надо: часы, которые тикают раз в 10 секунд (точность 10000)
|
||||
var lowResolutionClock = new ExtendedClock({
|
||||
template: 'h:m:s',
|
||||
precision: 10000
|
||||
});
|
||||
|
||||
lowResolutionClock.start();
|
||||
*/
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,13 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Класс "расширенные часы"
|
||||
|
||||
Есть реализация часиков на прототипах. Создайте класс, расширяющий её, добавляющий поддержку параметра `precision`, который будет задавать частоту тика в `setInterval`. Значение по умолчанию: `1000`.
|
||||
|
||||
- Для этого класс `Clock` надо унаследовать. Пишите ваш новый код в файле `extended-clock.js`.
|
||||
- Исходный класс `Clock` менять нельзя.
|
||||
- Пусть конструктор потомка вызывает конструктор родителя. Это позволит избежать проблем при расширении `Clock` новыми опциями.
|
||||
|
||||
P.S. Часики тикают в браузерной консоли (надо открыть её, чтобы увидеть).
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
Обратите внимание: константы состояний перенесены в прототип, чтобы `AnimatingMenu` их тоже унаследовал.
|
|
@ -0,0 +1,64 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script src="menu.js"></script>
|
||||
<script>
|
||||
function AnimatingMenu() {
|
||||
Menu.apply(this, arguments);
|
||||
}
|
||||
|
||||
AnimatingMenu.prototype = Object.create(Menu.prototype);
|
||||
|
||||
AnimatingMenu.prototype.STATE_ANIMATING = 2;
|
||||
|
||||
AnimatingMenu.prototype.open = function() {
|
||||
var self = this;
|
||||
|
||||
this._state = this.STATE_ANIMATING;
|
||||
|
||||
this._timer = setTimeout(function() {
|
||||
Menu.prototype.open.call(self);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
AnimatingMenu.prototype.close = function() {
|
||||
clearTimeout(this._timer);
|
||||
Menu.prototype.close.apply(this);
|
||||
};
|
||||
|
||||
AnimatingMenu.prototype._stateAsString = function() {
|
||||
|
||||
switch (this._state) {
|
||||
case this.STATE_ANIMATING:
|
||||
return 'анимация';
|
||||
|
||||
default:
|
||||
return Menu.prototype._stateAsString.call(this);
|
||||
}
|
||||
};
|
||||
|
||||
// тест, использование..
|
||||
var menu = new AnimatingMenu();
|
||||
|
||||
menu.showState(); // закрыто
|
||||
|
||||
menu.open();
|
||||
menu.showState(); // анимация
|
||||
|
||||
setTimeout(function() { // через 1 секунду
|
||||
menu.showState(); // открыто
|
||||
|
||||
menu.close();
|
||||
menu.showState(); // закрыто
|
||||
}, 1000);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,28 @@
|
|||
function Menu(state) {
|
||||
this._state = state || this.STATE_CLOSED;
|
||||
};
|
||||
|
||||
Menu.prototype.STATE_OPEN = 1;
|
||||
Menu.prototype.STATE_CLOSED = 0;
|
||||
|
||||
Menu.prototype.open = function() {
|
||||
this._state = this.STATE_OPEN;
|
||||
};
|
||||
|
||||
Menu.prototype.close = function() {
|
||||
this._state = this.STATE_CLOSED;
|
||||
};
|
||||
|
||||
Menu.prototype._stateAsString = function() {
|
||||
switch (this._state) {
|
||||
case this.STATE_OPEN:
|
||||
return 'открыто';
|
||||
|
||||
case this.STATE_CLOSED:
|
||||
return 'закрыто';
|
||||
}
|
||||
};
|
||||
|
||||
Menu.prototype.showState = function() {
|
||||
alert(this._stateAsString());
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script src="menu.js"></script>
|
||||
<script>
|
||||
var AnimatingMenu = Menu; // замените на ваш код для AnimatingMenu
|
||||
|
||||
// использование..
|
||||
|
||||
var menu = new AnimatingMenu();
|
||||
|
||||
menu.showState(); // закрыто
|
||||
|
||||
menu.open();
|
||||
menu.showState(); // анимация
|
||||
|
||||
setTimeout(function() {
|
||||
menu.showState(); // открыто
|
||||
|
||||
menu.close();
|
||||
menu.showState(); // закрыто (закрытие без анимации)
|
||||
}, 1000);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,28 @@
|
|||
function Menu(state) {
|
||||
this._state = state || Menu.STATE_CLOSED;
|
||||
};
|
||||
|
||||
Menu.STATE_OPEN = 1;
|
||||
Menu.STATE_CLOSED = 0;
|
||||
|
||||
Menu.prototype.open = function() {
|
||||
this._state = Menu.STATE_OPEN;
|
||||
};
|
||||
|
||||
Menu.prototype.close = function() {
|
||||
this._state = Menu.STATE_CLOSED;
|
||||
};
|
||||
|
||||
Menu.prototype._stateAsString = function() {
|
||||
switch (this._state) {
|
||||
case Menu.STATE_OPEN:
|
||||
return 'открыто';
|
||||
|
||||
case Menu.STATE_CLOSED:
|
||||
return 'закрыто';
|
||||
}
|
||||
};
|
||||
|
||||
Menu.prototype.showState = function() {
|
||||
alert(this._stateAsString());
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Меню с таймером для анимации
|
||||
|
||||
Есть класс `Menu`. У него может быть два состояния: открыто `STATE_OPEN` и закрыто `STATE_CLOSED`.
|
||||
|
||||
Создайте наследника `AnimatingMenu`, который добавляет третье состояние `STATE_ANIMATING`.
|
||||
|
||||
- При вызове `open()` состояние меняется на `STATE_ANIMATING`, а через 1 секунду, по таймеру, открытие завершается вызовом `open()` родителя.
|
||||
- Вызов `close()` при необходимости отменяет таймер анимации (назначаемый в `open`) и передаёт вызов родительскому `close`.
|
||||
- Метод `showState` для нового состояния выводит `"анимация"`, для остальных -- полагается на родителя.
|
||||
|
||||
[edit src="source" title="Исходный документ, вместе с тестом"]
|
|
@ -0,0 +1,24 @@
|
|||
**Нет, не распознает, выведет `false`.**
|
||||
|
||||
Свойство `constructor` содержится в `prototype` функции по умолчанию, интерпретатор не поддерживает его корректность. Посмотрим, чему оно равно и откуда оно будет взято в данном случае.
|
||||
|
||||
Порядок поиска свойства `rabbit.constructor`, по цепочке прототипов:
|
||||
|
||||
1. `rabbit` -- это пустой объект, в нём нет.
|
||||
2. `Rabbit.prototype` -- в него при помощи `Object.create` записан пустой объект, наследующий от `Animal.prototype`. Поэтому `constructor'а` в нём также нет.
|
||||
3. `Animal.prototype` -- у функции `Animal` свойство `prototype` никто не менял. Поэтому оно содержит `Animal.prototype.constructor == Animal`.
|
||||
|
||||
```js run
|
||||
function Animal() {}
|
||||
|
||||
function Rabbit() {}
|
||||
Rabbit.prototype = Object.create(Animal.prototype);
|
||||
|
||||
var rabbit = new Rabbit();
|
||||
|
||||
*!*
|
||||
alert( rabbit.constructor == Rabbit ); // false
|
||||
alert( rabbit.constructor == Animal ); // true
|
||||
*/!*
|
||||
```
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Что содержит constructor?
|
||||
|
||||
В коде ниже создаётся простейшая иерархия классов: `Animal -> Rabbit`.
|
||||
|
||||
Что содержит свойство `rabbit.constructor`? Распознает ли проверка в `alert` объект как `Rabbit`?
|
||||
|
||||
```js
|
||||
function Animal() {}
|
||||
|
||||
function Rabbit() {}
|
||||
Rabbit.prototype = Object.create(Animal.prototype);
|
||||
|
||||
var rabbit = new Rabbit();
|
||||
|
||||
alert( rabbit.constructor == Rabbit ); // что выведет?
|
||||
```
|
||||
|
340
1-js/9-object-inheritance/08-class-inheritance/article.md
Normal file
|
@ -0,0 +1,340 @@
|
|||
# Наследование классов в JavaScript
|
||||
|
||||
Наследование на уровне объектов в JavaScript, как мы видели, реализуется через ссылку `__proto__`.
|
||||
|
||||
Теперь поговорим о наследовании на уровне классов, то есть когда объекты, создаваемые, к примеру, через `new Admin`, должны иметь все методы, которые есть у объектов, создаваемых через `new User`, и ещё какие-то свои.
|
||||
|
||||
[cut]
|
||||
|
||||
## Наследование Array от Object
|
||||
|
||||
Для реализации наследования в наших классах мы будем использовать тот же подход, который принят внутри JavaScript.
|
||||
|
||||
Взглянем на него ещё раз на примере `Array`, который наследует от `Object`:
|
||||
|
||||

|
||||
|
||||
- Методы массивов `Array` хранятся в `Array.prototype`.
|
||||
- `Array.prototype` имеет прототипом `Object.prototype`.
|
||||
|
||||
Поэтому когда экземпляры класса `Array` хотят получить метод массива -- они берут его из своего прототипа, например `Array.prototype.slice`.
|
||||
|
||||
Если же нужен метод объекта, например, `hasOwnProperty`, то его в `Array.prototype` нет, и он берётся из `Object.prototype`.
|
||||
|
||||
Отличный способ "потрогать это руками" -- запустить в консоли команду `console.dir([1,2,3])`.
|
||||
|
||||
Вывод в Chrome будет примерно таким:
|
||||
|
||||

|
||||
|
||||
Здесь отчётливо видно, что сами данные и `length` находятся в массиве, дальше в `__proto__` идут методы для массивов `concat`, то есть `Array.prototype`, а далее -- `Object.prototype`.
|
||||
|
||||
```smart header="`console.dir` для доступа к свойствам"
|
||||
Обратите внимание, я использовал именно `console.dir`, а не `console.log`, поскольку `log` зачастую выводит объект в виде строки, без доступа к свойствам.
|
||||
```
|
||||
|
||||
## Наследование в наших классах
|
||||
|
||||
Применим тот же подход для наших классов: объявим класс `Rabbit`, который будет наследовать от `Animal`.
|
||||
|
||||
Вначале создадим два этих класса по отдельности, они пока что будут совершенно независимы.
|
||||
|
||||
`Animal`:
|
||||
|
||||
```js
|
||||
function Animal(name) {
|
||||
this.name = name;
|
||||
this.speed = 0;
|
||||
}
|
||||
|
||||
Animal.prototype.run = function(speed) {
|
||||
this.speed += speed;
|
||||
alert( this.name + ' бежит, скорость ' + this.speed );
|
||||
};
|
||||
|
||||
Animal.prototype.stop = function() {
|
||||
this.speed = 0;
|
||||
alert( this.name + ' стоит' );
|
||||
};
|
||||
```
|
||||
|
||||
`Rabbit`:
|
||||
|
||||
```js
|
||||
function Rabbit(name) {
|
||||
this.name = name;
|
||||
this.speed = 0;
|
||||
}
|
||||
|
||||
Rabbit.prototype.jump = function() {
|
||||
this.speed++;
|
||||
alert( this.name + ' прыгает' );
|
||||
};
|
||||
|
||||
var rabbit = new Rabbit('Кроль');
|
||||
```
|
||||
|
||||
Для того, чтобы наследование работало, объект `rabbit = new Rabbit` должен использовать свойства и методы из своего прототипа `Rabbit.prototype`, а если их там нет, то -- свойства и метода родителя, которые хранятся в `Animal.prototype`.
|
||||
|
||||
Если ещё короче -- порядок поиска свойств и методов должен быть таким: `rabbit -> Rabbit.prototype -> Animal.prototype`, по аналогии с тем, как это сделано для объектов и массивов.
|
||||
|
||||
Для этого можно поставить ссылку `__proto__` с `Rabbit.prototype` на `Animal.prototype`.
|
||||
|
||||
Можно сделать это так:
|
||||
```js
|
||||
Rabbit.prototype.__proto__ = Animal.prototype;
|
||||
```
|
||||
|
||||
Однако, прямой доступ к `__proto__` не поддерживается в IE10-, поэтому для поддержки этих браузеров мы используем функцию `Object.create`. Она либо встроена либо легко эмулируется во всех браузерах.
|
||||
|
||||
Класс `Animal` остаётся без изменений, а `Rabbit.prototype` мы будем создавать с нужным прототипом, используя `Object.create`:
|
||||
|
||||
```js no-beautify
|
||||
function Rabbit(name) {
|
||||
this.name = name;
|
||||
this.speed = 0;
|
||||
}
|
||||
|
||||
*!*
|
||||
// задаём наследование
|
||||
Rabbit.prototype = Object.create(Animal.prototype);
|
||||
*/!*
|
||||
|
||||
// и добавим свой метод (или методы...)
|
||||
Rabbit.prototype.jump = function() { ... };
|
||||
```
|
||||
|
||||
Теперь выглядеть иерархия будет так:
|
||||
|
||||

|
||||
|
||||
В `prototype` по умолчанию всегда находится свойство `constructor`, указывающее на функцию-конструктор. В частности, `Rabbit.prototype.constructor == Rabbit`. Если мы рассчитываем использовать это свойство, то при замене `prototype` через `Object.create` нужно его явно сохранить:
|
||||
|
||||
```js
|
||||
Rabbit.prototype = Object.create(Animal.prototype);
|
||||
Rabbit.prototype.constructor = Rabbit;
|
||||
```
|
||||
|
||||
## Полный код наследования
|
||||
|
||||
Для наглядности -- вот итоговый код с двумя классами `Animal` и `Rabbit`:
|
||||
|
||||
```js
|
||||
// 1. Конструктор Animal
|
||||
function Animal(name) {
|
||||
this.name = name;
|
||||
this.speed = 0;
|
||||
}
|
||||
|
||||
// 1.1. Методы -- в прототип
|
||||
|
||||
Animal.prototype.stop = function() {
|
||||
this.speed = 0;
|
||||
alert( this.name + ' стоит' );
|
||||
}
|
||||
|
||||
Animal.prototype.run = function(speed) {
|
||||
this.speed += speed;
|
||||
alert( this.name + ' бежит, скорость ' + this.speed );
|
||||
};
|
||||
|
||||
// 2. Конструктор Rabbit
|
||||
function Rabbit(name) {
|
||||
this.name = name;
|
||||
this.speed = 0;
|
||||
}
|
||||
|
||||
// 2.1. Наследование
|
||||
Rabbit.prototype = Object.create(Animal.prototype);
|
||||
Rabbit.prototype.constructor = Rabbit;
|
||||
|
||||
// 2.2. Методы Rabbit
|
||||
Rabbit.prototype.jump = function() {
|
||||
this.speed++;
|
||||
alert( this.name + ' прыгает, скорость ' + this.speed );
|
||||
}
|
||||
```
|
||||
|
||||
Как видно, наследование задаётся всего одной строчкой, поставленной в правильном месте.
|
||||
|
||||
Обратим внимание: `Rabbit.prototype = Object.create(Animal.prototype)` присваивается сразу после объявления конструктора, иначе он перезатрёт уже записанные в прототип методы.
|
||||
|
||||
````warn header="Неправильный вариант: `Rabbit.prototype = new Animal`"
|
||||
В некоторых устаревших руководствах предлагают вместо `Object.create(Animal.prototype)` записывать в прототип `new Animal`, вот так:
|
||||
|
||||
```js
|
||||
// вместо Rabbit.prototype = Object.create(Animal.prototype)
|
||||
Rabbit.prototype = new Animal();
|
||||
```
|
||||
|
||||
Частично, он рабочий, поскольку иерархия прототипов будет такая же, ведь `new Animal` -- это объект с прототипом `Animal.prototype`, как и `Object.create(Animal.prototype)`. Они в этом плане идентичны.
|
||||
|
||||
Но у этого подхода важный недостаток. Как правило мы не хотим создавать `Animal`, а хотим только унаследовать его методы!
|
||||
|
||||
Более того, на практике создание объекта может требовать обязательных аргументов, влиять на страницу в браузере, делать запросы к серверу и что-то ещё, чего мы хотели бы избежать. Поэтому рекомендуется использовать вариант с `Object.create`.
|
||||
````
|
||||
|
||||
## Вызов конструктора родителя
|
||||
|
||||
Посмотрим внимательно на конструкторы `Animal` и `Rabbit` из примеров выше:
|
||||
|
||||
```js
|
||||
function Animal(name) {
|
||||
this.name = name;
|
||||
this.speed = 0;
|
||||
}
|
||||
|
||||
function Rabbit(name) {
|
||||
this.name = name;
|
||||
this.speed = 0;
|
||||
}
|
||||
```
|
||||
|
||||
Как видно, объект `Rabbit` не добавляет никакой особенной логики при создании, которой не было в `Animal`.
|
||||
|
||||
Чтобы упростить поддержку кода, имеет смысл не дублировать код конструктора `Animal`, а напрямую вызвать его:
|
||||
|
||||
```js
|
||||
function Rabbit(name) {
|
||||
Animal.apply(this, arguments);
|
||||
}
|
||||
```
|
||||
|
||||
Такой вызов запустит функцию `Animal` в контексте текущего объекта, со всеми аргументами, она выполнится и запишет в `this` всё, что нужно.
|
||||
|
||||
Здесь можно было бы использовать и `Animal.call(this, name)`, но `apply` надёжнее, так как работает с любым количеством аргументов.
|
||||
|
||||
## Переопределение метода
|
||||
|
||||
Итак, `Rabbit` наследует `Animal`. Теперь если какого-то метода нет в `Rabbit.prototype` -- он будет взят из `Animal.prototype`.
|
||||
|
||||
В `Rabbit` может понадобиться задать какие-то методы, которые у родителя уже есть. Например, кролики бегают не так, как остальные животные, поэтому переопределим метод `run()`:
|
||||
|
||||
```js
|
||||
Rabbit.prototype.run = function(speed) {
|
||||
this.speed++;
|
||||
this.jump();
|
||||
};
|
||||
```
|
||||
|
||||
Вызов `rabbit.run()` теперь будет брать `run` из своего прототипа:
|
||||
|
||||

|
||||
|
||||
### Вызов метода родителя внутри своего
|
||||
|
||||
Более частая ситуация -- когда мы хотим не просто заменить метод на свой, а взять метод родителя и расширить его. Скажем, кролик бежит так же, как и другие звери, но время от времени подпрыгивает.
|
||||
|
||||
Для вызова метода родителя можно обратиться к нему напрямую, взяв из прототипа:
|
||||
|
||||
```js
|
||||
Rabbit.prototype.run = function() {
|
||||
*!*
|
||||
// вызвать метод родителя, передав ему текущие аргументы
|
||||
Animal.prototype.run.apply(this, arguments);
|
||||
*/!*
|
||||
this.jump();
|
||||
}
|
||||
```
|
||||
|
||||
Обратите внимание на вызов через `apply` и явное указание контекста.
|
||||
|
||||
Если вызвать просто `Animal.prototype.run()`, то в качестве `this` функция `run` получит `Animal.prototype`, а это неверно, нужен текущий объект.
|
||||
|
||||
## Итого
|
||||
|
||||
- Для наследования нужно, чтобы "склад методов потомка" (`Child.prototype`) наследовал от "склада метода родителей" (`Parent.prototype`).
|
||||
|
||||
Это можно сделать при помощи `Object.create`:
|
||||
|
||||
Код:
|
||||
|
||||
```js
|
||||
Rabbit.prototype = Object.create(Animal.prototype);
|
||||
```
|
||||
- Для того, чтобы наследник создавался так же, как и родитель, он вызывает конструктор родителя в своём контексте, используя `apply(this, arguments)`, вот так:
|
||||
|
||||
```js
|
||||
function Rabbit(...) {
|
||||
Animal.apply(this, arguments);
|
||||
}
|
||||
```
|
||||
- При переопределении метода родителя в потомке, к исходному методу можно обратиться, взяв его напрямую из прототипа:
|
||||
|
||||
```js
|
||||
Rabbit.prototype.run = function() {
|
||||
var result = Animal.prototype.run.apply(this, ...);
|
||||
// result -- результат вызова метода родителя
|
||||
}
|
||||
```
|
||||
|
||||
Структура наследования полностью:
|
||||
|
||||
```js run
|
||||
*!*
|
||||
// --------- Класс-Родитель ------------
|
||||
*/!*
|
||||
// Конструктор родителя пишет свойства конкретного объекта
|
||||
function Animal(name) {
|
||||
this.name = name;
|
||||
this.speed = 0;
|
||||
}
|
||||
|
||||
// Методы хранятся в прототипе
|
||||
Animal.prototype.run = function() {
|
||||
alert(this.name + " бежит!")
|
||||
}
|
||||
|
||||
*!*
|
||||
// --------- Класс-потомок -----------
|
||||
*/!*
|
||||
// Конструктор потомка
|
||||
function Rabbit(name) {
|
||||
Animal.apply(this, arguments);
|
||||
}
|
||||
|
||||
// Унаследовать
|
||||
*!*
|
||||
Rabbit.prototype = Object.create(Animal.prototype);
|
||||
*/!*
|
||||
|
||||
// Желательно и constructor сохранить
|
||||
Rabbit.prototype.constructor = Rabbit;
|
||||
|
||||
// Методы потомка
|
||||
Rabbit.prototype.run = function() {
|
||||
// Вызов метода родителя внутри своего
|
||||
Animal.prototype.run.apply(this);
|
||||
alert( this.name + " подпрыгивает!" );
|
||||
};
|
||||
|
||||
// Готово, можно создавать объекты
|
||||
var rabbit = new Rabbit('Кроль');
|
||||
rabbit.run();
|
||||
```
|
||||
|
||||
Такое наследование лучше функционального стиля, так как не дублирует методы в каждом объекте.
|
||||
|
||||
Кроме того, есть ещё неявное, но очень важное архитектурное отличие.
|
||||
|
||||
Зачастую вызов конструктора имеет какие-то побочные эффекты, например влияет на документ. Если конструктор родителя имеет какое-то поведение, которое нужно переопределить в потомке, то в функциональном стиле это невозможно.
|
||||
|
||||
Иначе говоря, в функциональном стиле в процессе создания `Rabbit` нужно обязательно вызывать `Animal.apply(this, arguments)`, чтобы получить методы родителя -- и если этот `Animal.apply` кроме добавления методов говорит: "Му-у-у!", то это проблема:
|
||||
|
||||
```js
|
||||
function Animal() {
|
||||
this.walk = function() {
|
||||
alert('walk')
|
||||
};
|
||||
alert( 'Му-у-у!' );
|
||||
}
|
||||
|
||||
function Rabbit() {
|
||||
Animal.apply(this, arguments); // как избавиться от мычания, но получить walk?
|
||||
}
|
||||
```
|
||||
|
||||
...Которой нет в прототипном подходе, потому что в процессе создания `new Rabbit` мы вовсе не обязаны вызывать конструктор родителя. Ведь методы находятся в прототипе.
|
||||
|
||||
Поэтому прототипный подход стоит предпочитать функциональному как более быстрый и универсальный. А что касается красоты синтаксиса -- она сильно лучше в новом стандарте ES6, которым можно пользоваться уже сейчас, если взять транслятор [babeljs](https://babeljs.io/).
|
||||
|
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 34 KiB |