Доброго времени суток, друзья!
В наших прошлом уроке мы c Вами начали рассматривать реализацию одного из свойств ООП - Наследования с помощью кода на JavaScript. Мы создали Родителя, двух его Потомков, проследили передачу методов и свойств, убедились, что они специфичны для каждого из потомков и изменение свойств или методов в одном потомке никак не влияет на одноименное свойство или метод в остальных потомках. Кроме того, мы посмотрели, как свойствам можно присваивать значения непосредственно при создании Потомка и как мы можем оперировать параметрами в самих методах.
Однако в конце урока я сказал, что, то что мы рассмотрели полноценным Наследованием назвать можно, но с натяжкой. По большому счету мы просто создавали потомков по образу и подобию нашего Родителя. В чем же тут дело? Давайте разберемся и познаем наконец-то всю силу Наследования в JavaScript.
УРОК 32. ООП. Наследование, Часть II. PROTOTYPE.
Итак, еще раз, у нас есть Родитель Animal
и есть два его Потомка: кот {cat}
и собака {dog}
. То есть у нас получился один уровень наследования (от родителя сразу пошли потомки). Однако, все мы знаем, что в жизни дело обстоит несколько сложнее, и те же самые коты, например, они кончено и безусловно в первую очередь животные, но помимо этого они еще и представители некоторой породы, у них есть цвет, у некоторых есть шерсть, у других она отсутствует, у каждых своих размер, вес, рост и так далее. То есть большое количество признаков для кота. То же самое актуально и для собаки. И все было бы хорошо, если у нас ожидаются только собаки и только коты. Они хоть и разные животные, но большинство признаков у них схожи. Но что делать если нам потребуется описать не только собаку или кота, но еще и птицу? Она разительно отличается и от собаки, и от кота и набор свойств и методов у нее будет свой. А если задача будет стоять так, чтобы мы могли описать вообще любое животное, насекомое или рептилию, какую захотим. То мы просто можем утонуть в количестве их признаков и методов. Что делать в таком случае?
Безусловно мы можем попробовать все эти признаки и методы описать сразу в Родителе Animal
. Но, во-первых, это получится бесконечно огромная функция-конструктор и с ней будет очень неудобно работать. Во-вторых, это будет не совсем правильное описание модели. К примеру, захотим мы добавить змею. А она у нас не ходит, а ползает. Зачем ей тогда спрашивается метод walk
и т.д.? Какой выход из сложившейся ситуации?
Мы можем создать Наследование в два уровня вместо одного. Например, вынести совсем уж общие свойства и методы в Родителя которого назовем Creature
(создание). От него все общие признаки унаследует еще один Родитель который будет отдельно для каждого создания, создать которое мы захотим. Например, Родитель Cat
- для кошек, Dog
- для собак, Snake
- для змей, Bird
- для птиц и так далее. И вот уже от этих Родителей для конкретного вида животных мы будем создавать объекты. Давайте представим схему, для визуального восприятия:
То есть мы создали Родителя Creature
с методами sleep
и eat
. От Creature
мы создали еще Родителей для кота, собаки, змеи и птицы, которые должны будут унаследовать методы из Creature
, а затем мы уже создадим потомков для каждого Родителя и эти Потомки в свою очередь уже должны будут унаследовать методы из Creature
а также методы и свойства из числа Родителей: Cat
, Dog
, Snake
и Bird
.
Давайте реализуем это в коде. Для начала создадим Родителя Creature
:
/* Родитель, Класс, Функция Конструктор */
var Creature = function() {
}
Ничего необычного, мы делали это в прошлом уроке точно так же. Теперь давайте создадим Родителей для наших существ:
/* Родитель, Класс, Функция Конструктор */
var Creature = function() {
}
/* Родитель, Класс, Функция Конструктор (2-го уровня) */
var Cat = function() {
}
var Dog = function() {
}
var Snake = function() {
}
var Bird = function() {
}
И снова нет ничего сложного. Все идет по аналогии с нашим предыдущим уроком. Теперь давайте согласно теме, зададим нашим родителям методы и свойства:
/* Родитель, Класс, Функция Конструктор */
var Creature = function() {
this.sleep = function() {
console.log('спит...');
}
this.eat = function() {
console.log('кушает...');
}
}
/* Родитель, Класс, Функция Конструктор (2-го уровня) */
var Cat = function() {
this.tail = 1;
this.paws = 4;
this.walk = function() {
console.log('ходит...');
}
}
var Dog = function() {
this.tail = 1;
this.paws = 4;
this.walk = function() {
console.log('ходит...');
}
}
var Snake = function() {
this.body = 1;
this.head = 1;
this.crawl = function() {
console.log('ползает...');
}
}
var Bird = function() {
this.tail = 1;
this.wings = 4;
this.fly = function() {
console.log('летает...');
}
}
Тут у нас вроде бы тоже нет ничего нового, все методы и свойства задаются через контекст this
который при создании потомков будет указывать на самого потомка и теперь мы вроде как можем начать их создавать. Давайте создадим по одному потомку каждой функции-конструктора:
/* Родитель, Класс, Функция Конструктор */
var Creature = function() {
this.sleep = function() {
console.log('спит...');
}
this.eat = function() {
console.log('кушает...');
}
}
/* Родитель, Класс, Функция Конструктор (2-го уровня) */
var Cat = function() {
this.tail = 1;
this.paws = 4;
this.walk = function() {
console.log('ходит...');
}
}
var Dog = function() {
this.tail = 1;
this.paws = 4;
this.walk = function() {
console.log('ходит...');
}
}
var Snake = function() {
this.body = 1;
this.head = 1;
this.crawl = function() {
console.log('ползает...');
}
}
var Bird = function() {
this.tail = 1;
this.wings = 4;
this.fly = function() {
console.log('летает...');
}
}
/* Потомки */
var cat = new Cat();
var dog = new Dog();
var snake = new Snake();
var bird = new Bird();
Вот у нас уже есть четыре Потомка, и если мы посмотрим, как они выглядят в консоли, то мы убедимся, что каждый из них унаследовал свойства и методы непосредственно от своего Родителя:
Мы видим, что это действительно так. Однако, постойте, нам же надо чтобы у наших потомков были наследованы еще и методы из Родителя первого уровня Creature
! Давайте решать эту задачу.
Смотрите, когда мы производим Наследование от Родителя к Потомку мы вполне можем использовать контекст this
как мы уже это и делали. Но в случаем, когда нам нужно унаследовать что-либо от Родителя к Родителю, в нашем случае от Creature
к Cat
, Dog
, Snake
и Bird
мы уже не можем использовать контекст, так как в этой связи нигде в промежутке мы не создаем Потомка. И в этом случае нам на помощь приходит свойство, которое называется Prototype.
Prototype.
Это свойство встроенное. То есть вам не надо его специально создавать, оно уже сразу присутствует во всех Родителях (Классах, Функциях-конструкторах). Значение у этого свойства – Объект (Object). По умолчанию этот объект пустой. Логика работы с этим Объектом не сложная. Например, если мы знаем, что методы sleep
и eat
в Родителе Creature
, должны будут наследоваться другими функциями-родителями, то для начала нам надо вынести эти методы в prototype и убедится, что они там появились. Давайте это сделаем:
/* Родитель, Класс, Функция Конструктор */
var Creature = function() {
}
Creature.prototype = {
sleep: function() {
console.log(this.name + ' спит...');
},
eat: function() {
console.log(this.name + ' кушает...');
}
}
/* Родитель, Класс, Функция Конструктор (2-го уровня) */
var Cat = function() {
this.tail = 1;
this.paws = 4;
this.walk = function() {
console.log('ходит...');
}
}
var Dog = function() {
this.tail = 1;
this.paws = 4;
this.walk = function() {
console.log('ходит...');
}
}
var Snake = function() {
this.body = 1;
this.head = 1;
this.crawl = function() {
console.log('ползает...');
}
}
var Bird = function() {
this.tail = 1;
this.wings = 4;
this.fly = function() {
console.log('летает...');
}
}
/* Потомки */
var cat = new Cat();
var dog = new Dog();
var snake = new Snake();
var bird = new Bird();
Обратите внимание как мы преобразовали код. Мы вынесли методы, которые мы знаем должны будут наследовать в свойство prototype
Родителя Creature
. То есть свойство prototype у этой функции-конструктора имеет значение объекта с двумя методами. Проверим в консоли можем ли мы получить к ним доступ:
Можем. Отлично. Идем дальше. Теперь нам надо каким-то образом сообщить нашим Родителям Cat
, Dog
, Snake
и Bird
что они должны унаследовать эти методы из prototype Creature
. Вспомним что все эти функции-конструкторы тоже Родители Значит у них у всех тоже есть свойство prototype. И нам надо сделать так, что эти свойства ссылались на одноименное свойство функции-родителя Creature
. Давайте это сделаем:
/* Родитель, Класс, Функция Конструктор */
var Creature = function() {
}
Creature.prototype = {
sleep: function() {
console.log(this.name + ' спит...');
},
eat: function() {
console.log(this.name + ' кушает...');
}
}
/* Родитель, Класс, Функция Конструктор (2-го уровня) */
var Cat = function() {
this.tail = 1;
this.paws = 4;
this.walk = function() {
console.log('ходит...');
}
}
Cat.prototype = Creature.prototype;
var Dog = function() {
this.tail = 1;
this.paws = 4;
this.walk = function() {
console.log('ходит...');
}
}
Dog.prototype = Creature.prototype;
var Snake = function() {
this.body = 1;
this.head = 1;
this.crawl = function() {
console.log('ползает...');
}
}
Snake.prototype = Creature.prototype;
var Bird = function() {
this.tail = 1;
this.wings = 4;
this.fly = function() {
console.log('летает...');
}
}
Bird.prototype = Creature.prototype;
/* Потомки */
var cat = new Cat();
var dog = new Dog();
var snake = new Snake();
var bird = new Bird();
Видите, мы каждому свойству prototype, каждого Родителя (2-уровня) говорим что он равен прототипу Creature
. И теперь, если мы взглянем в консоль, к примеру, на Родителя Snake и его Потомка, то увидим следующую картину:
Сама функция Snake
осталась без изменений. Её prototype приобрел методы, переданные из Creature
. А Потомок snake
приобрел методы как из своего непосредственного Родителя, так и из Родителя своего Родителя. Осталось проверить работоспособность этих методов. Для этого давайте добавим к нашим Родителям свойство this.name
:
/* Родитель, Класс, Функция Конструктор */
var Creature = function() {
}
Creature.prototype = {
sleep: function() {
console.log(this.name + ' спит...');
},
eat: function() {
console.log(this.name + ' кушает...');
}
}
/* Родитель, Класс, Функция Конструктор (2-го уровня) */
var Cat = function(name) {
this.name = name;
this.tail = 1;
this.paws = 4;
this.walk = function() {
console.log('ходит...');
}
}
Cat.prototype = Creature.prototype;
var Dog = function(name) {
this.name = name;
this.tail = 1;
this.paws = 4;
this.walk = function() {
console.log('ходит...');
}
}
Dog.prototype = Creature.prototype;
var Snake = function(name) {
this.name = name;
this.body = 1;
this.head = 1;
this.crawl = function() {
console.log('ползает...');
}
}
Snake.prototype = Creature.prototype;
var Bird = function(name) {
this.name = name;
this.tail = 1;
this.wings = 4;
this.fly = function() {
console.log('летает...');
}
}
Bird.prototype = Creature.prototype;
/* Потомки */
var cat = new Cat('кот');
var dog = new Dog('пёс');
var snake = new Snake('змей');
var bird = new Bird('птица');
Мы добавили всем нашим Родителям новое свойство, а его значение указали при создании Потомков. И теперь давайте проверим, действительно ли мы можем использовать методы, которые нам достались по наследству аж от Creature
.
Как видим все методы работают. Мы произвели наследование используя свойство прототип (prototype). И на последок, нам надо понять логику которая называется прототипной цепочкой. Если мы посмотрим в браузере какого-нибудь потомка, например, птицу:
то увидим, что изначально в наборе его методов и свойств нет ни метода sleep
ни метода eat
. И тем не менее, когда мы вызываем один из этих методов, мы получаем его исполнение, а не ошибку:
Почему так? Когда мы обращаемся к какому-либо методу, JavaScript запускает ту самую логику прототипной цепочки. Сначала он ищет этот метод внутри самого объекта, если там его нет, он ищет его внутри прототипа функции, от которой этот объект был произведен, если там его нет, тогда он идет в прототип функции на уровень выше и так до самого первого прототипа с которого все началось. И вот только в случае если этот метод не был обнаружен ни в одном из прототипов, JavaScript выдаст ошибку, в противном же случае он выполнит первый попавшийся метод.
Ну а на сегодня все. В следующем уроки мы посмотри как с помощью кода реализуется Полиморфизм и Инкапсуляция.
Ссылки на предыдущие уроки:
Урок 1 - Окружение.,
Урок 2 - Некоторые особенности синтаксиса.,
Урок 3 - Переменные.,
Урок 4 - Типы переменных, как основа для их взаимодействия.,
Урок 5 - Операции с переменными одного типа.,
Урок 6 - Операции с переменными одного типа. Часть II.,
Урок 7 - Взаимодействие переменных с разными типами.,
Урок 8 - Взаимодействие переменных разного типа. часть II.,
Урок 9 - Взаимодействие переменных разного типа. Часть III.,
Урок 10 - Другие возможные взаимодействия между переменными.,
Урок 11 - Другие возможные взаимодействия между переменными. Часть II.,
Урок 12 - Другие возможные взаимодействия между переменными. Операторы присваивания.,
Урок 13 - Другие возможные взаимодействия между переменными. Операторы сравнения.,
Урок 14 - Сложные переменные. Array и Object.,
Урок 15 - Условные операторы.),
Урок 16 - Циклы.,
Урок 17 - Циклы. Часть II.,
Урок 18 - Функции.,
Урок 19 - Функции. Часть II.,
Урок 20 - Профилирование. Функции, часть III.,
Урок 21 - Функции, Часть IV. Аргументы.,
Урок 22 - Objects (Объекты).,
Урок 23 - Встроенные функции и объекты.,
Урок 24 - Встроенные функции и Объекты, Часть II. Глобальные функции и переменные.,
Урок 25 - Встроенные функции и Объекты, Часть III. Document Object Model.,
Урок 26 - Встроенные функции и Объекты, Часть III. Document Object Model.
Урок 27 - Встроенные объекты. Объект Style, Events, Часть II.
Урок 28 - Встроенная переменная this. Глобальная и локальная области видимости.
Урок 29 - Объектно-ориентированное Программирование. Введение.
Урок 30. Объектно-ориентированное Программирование. Часть II. Полиморфизм.
Урок 31. OОП. Наследование, Часть I. Оператор new.
Отлично написано!
Благодарю
Мною овладела идея привести на проект голос 10.000 подписчиков пожелайте мне удачи..........
good luck
Давненько вас не было! Но рад что вы снова с нами)))
Благодарю.
Работа... :)
Эх эта работа! С ней не так то много времени, уделяем голосу, а хотелось бы больше!!!