Итак, Rabbit наследует Animal . Теперь если какого‑то метода нет в Rabbit.prototype – он будет взят из Animal.prototype .
В Rabbit может понадобиться задать какие‑то методы, которые у родителя уже есть. Например, кролики бегают не так, как остальные животные, поэтому переопределим метод run() :
Rabbit.prototype.run = function(speed) { this.speed++;
this.jump();
};
Вызов rabbit.run() теперь будет брать run из своего прототипа:
Вызов метода родителя внутри своего
Более частая ситуация – когда мы хотим не просто заменить метод на свой, а взять метод родителя и расширить его. Скажем, кролик бежит так же, как и другие звери, но время от времени подпрыгивает.
Для вызова метода родителя можно обратиться к нему напрямую, взяв из прототипа:
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 : Код:
Rabbit.prototype = Object.create(Animal.prototype);
Для того, чтобы наследник создавался так же, как и родитель, он вызывает конструктор родителя в своём контексте, используя apply(this, arguments) , вот так:
function Rabbit(...) { Animal.apply(this, arguments);
}
При переопределении метода родителя в потомке, к исходному методу можно обратиться, взяв его напрямую из прототипа:
Rabbit.prototype.run = function() {
var result = Animal.prototype.run.apply(this, ...);
// result ‐‐ результат вызова метода родителя
}
Структура наследования полностью:
// ‐‐‐‐‐‐‐‐‐ Класс‐Родитель ‐‐‐‐‐‐‐‐‐‐‐‐
// Конструктор родителя пишет свойства конкретного объекта 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 кроме добавления методов говорит: «Му‑у‑у!», то это проблема:
function Animal() { this.walk = function() {
alert('walk')
};
alert( 'Му‐у‐у!' );
}
function Rabbit() {
Animal.apply(this, arguments); // как избавиться от мычания, но получить walk?
}
…Которой нет в прототипном подходе, потому что в процессе создания new Rabbit мы вовсе не обязаны вызывать конструктор родителя. Ведь методы находятся в прототипе.
Поэтому прототипный подход стоит предпочитать функциональному как более быстрый и универсальный. А что касается красоты синтаксиса – она сильно лучше в новом стандарте ES6, которым можно пользоваться уже сейчас, если взять транслятор babeljs .
✔ Задачи
Do'stlaringiz bilan baham: |