#1 Прототипное наследование в Javascript

poster
В этом уроке мы разберем как реализуются классы, что такое прототипы, как создавать экземпляры классов и как реализовывать наследование в Javascript.
Понравилось? Поделитесь с друзьями!
Понравилось?
Поделитесь с друзьями!
Комментарии
Текст видео

Всем привет. Сегодня мы с вами разберем как реализовываются классы в Javascript и что такое прототипное наследование. Вообще как таковых классов в Javascript нет. Их реализуют именно с помощью прототипного наследования.

Классы в Javascript

Для начала давайте напишем обычную функцию

var track = function () {

}

Так как любая функция в Javascript является конструктором, то мы можем вызвать ее с помощью оператора new, чтобы создать экземпляр класса.

new track()

вернет нам новый обьект, который является экземпляром класса.

Конструкторы в Javacript пишут с большой буквы, чтобы легко отличить их от обычных функций.

var Track = function () {
}

Итак, давайте запомним с вами следующие понятия. Все, что мы сейчас будем описывать называется класс, функция на которую мы вызываем оператор new - называется конструктор (т.е. наш Track это конструктор), то, что мы получаем в результате вызова new - является экземпляром класса.

Давайте сейчас создадим два экземпляра класса Track, которые будут на вход принимать параметры в виде обьекта.

var track01 = new Track({
  name: 'track01',
  url: 'track01.mp3'
});

var track02 = new Track({
  name: 'track02',
  url: 'track02.mp3'
});

Если мы выведем track01 и track02 в консоль - мы увидим, что у нас создались два пустых обьекта.

console.log(track01);
console.log(track02);

Теперь давайте в нашем конструкторе добавим аргумент params и выведем его в консоль

var Track = function (params) {
  console.log(params);
}

Как мы видим в консоли - у нас передаются параметры в конструктор и мы можем с ними дальше работать. Переменная this внутри конструктора является контекстом экземпляра и к полям экземпляра можно обращатся через точку. Поэтому мы можем присвоить this.name и this.url, чтобы они были доступны в наших экземплярах класса.

var Track = function (params) {
  this.name = params.name;
  this.url = params.url;
}

Теперь, если мы обновим страницу мы увидим, что у нас создались экземпляры класса Track с заполненными полями name и url.

Теперь мы можем обращаться с полям экземпляров, например, track01.name или track01.url.

Теперь давайте опишем метод playTrack, чтобы каждый экземпляр класса мог играть.

var Track = function (params) {
  this.name = params.name;
  this.url = params.url;

  this.playTrack = function () {
    console.log('We started playing');
  }
}

Если мы попробуем вывести this внутри метода playTrack, то увидим что this является экземпляром нашего класса.

console.log('We started playing', this);

Мы можем, например, написать this.name для того, чтобы вывести имя трека.

console.log('We started playing', this.name);

Теперь давайте вызовем

track01.playTrack();

Как мы видим нам вывелось в консоль

We started playing track01

Если мы вызовем этот метод на второй трек, то получим

We started playing track02

Метод класса можно обьявлять не только в конструкторе, как мы только что сделали, но и через свойство prototype.

var Track = function (params) {
  this.name = params.name;
  this.url = params.url;

  //this.playTrack = function () {
    //console.log('We started playing', this.name);
  //}
}

Track.prototype.playTrack = function () {
  console.log('We started playing', this.name);
}

Если мы попробуем запустить наш код, то мы увидим, что он работает абсолютно точно так же.

В чем же различие? Единственная разница в том, что когда метод обьявлен через prototype, а не внутри конструктора, то мы можем его переопределить.

Если мы напишем в консоли

Track.prototype

то это выведет нам обьект с функцией playTrack внутри.

Если мы закомментируем код с prototype и раскомментируем код playTrack внутри конструктора, то мы увидим что Track.prototype в консоли - это пустой обьект, а значит функцию playTrack мы никак не можем изменить

var Track = function (params) {
  this.name = params.name;
  this.url = params.url;

  this.playTrack = function () {
    console.log('We started playing', this.name);
  }
};

//Track.prototype.playTrack = function () {
  //console.log('We started playing', this.name);
//};

###Наследование:

В ООП нас конечно же интересует наследование. Давайте представим, что нам нужен класс YoutubeTrack, который имеет дополнительное поле image и наследуется от класса Track.

var YoutubeTrack = function (params) {
};

var youtubeTrack01 = new YoutubeTrack({
    name: 'youtubeTrack01',
    url: 'youtubeTrack01.mp3'
});

var youtubeTrack02 = new YoutubeTrack({
    name: 'youtubeTrack02',
    url: 'youtubeTrack02.mp3'
});

Мы хотим, чтобы наш конструктор YoutubeTrack выполнял все ту же логику, что и Track только с небольшими изменениями. Для этого воспользуемся методом apply. Для того, чтобы вызвать конструктор Track внутри YoutubeTrack напишем

var YoutubeTrack = function (params) {
  Track.apply(this, arguments);
};

Что это делает? Apply вызывает функцию Track (в данном случае это конструктор), передавая контекстом this, т.е. контекст YoutubeTrack и передаем все аргументы, которые будут переданы в наш YoutubeTrack.

Давайте выведем в консоль youtubeTrack01 и youtubeTrack02

console.log(youtubeTrack01);
console.log(youtubeTrack02);

Мы видим, что все работает точно так же и name и url выводятся как поля обьектов в консоль, которые у нас засетились из конструктора Track.

Если же мы закоментируем код с Track.apply, то мы увидим, что у нас создались пустые обьекты.

Теперь мы хотим добавить еще одно поле к YoutubeTrack. Назовем его image.

var YoutubeTrack = function (params) {
  Track.apply(this, arguments);
  this.image = params.image;
};

Так же при создании экземпляров класса YoutubeTrack добавим image

var youtubeTrack01 = new YoutubeTrack({
    name: 'youtubeTrack01',
    url: 'youtubeTrack01.mp3',
    image: 'youtubeTrack01.jpg'
});

var youtubeTrack02 = new YoutubeTrack({
    name: 'youtubeTrack02',
    url: 'youtubeTrack02.mp3',
    image: 'youtubeTrack01.jpg'
});

Как мы видим теперь в консоли у нас выводится еще и image в YoutubeTrack обьектах.

Теперь мы хотим, чтобы все методы, которые нам доступны в классе Track, были так же доступны в YoutubeTrack.

Как мы можем видеть, в экземплярах трека у нас есть функция playTrack и мы хотим, чтобы она была доступна в YoutubeTrack.

Для этого нам нужно реализовать наследование.

YoutubeTrack.prototype = Object.create(Track.prototype);

Мы присваеваем в YoutubeTrack.prototype обьект, которые мы создаем с Track.prototype.

Как мы видим в консоли, у нас появился метод playTrack у экземпляров YoutubeTrack

Теперь мы легко можем вызвать

youtubeTrack01.playTrack();

и будет написано

We started playing youtubeTrack01

Но есть один минус. Если мы напишем в консоли

youtubeTrack.prototype.constructor

мы увидим здесь не конструктор youtubeTrack, а конструктор Track. Как это исправить?

YoutubeTrack.prototype = Object.create(Track.prototype);
YoutubeTrack.prototype.constructor = YoutubeTrack;

Этим мы возвращаем ссылку обратно на конструктор YoutubeTrack как нам необходимо. Теперь если мы посмотрим в консоль, то у нас вызывается правильный конструктор.

Как мы видим теперь у всех экземпляров класса есть метод playTrack и если нам необходимо - мы можем его переопределить.

YoutubeTrack.prototype.playTrack = function () {
  console.log('Hello youtube', this.name);
};

Теперь мы можем вызвать свой метод, который нам необходим.

Только зарегистрированные пользователи могут оставлять комментарии.  Войдите, пожалуйста.
Antohnio
2 лет назад
Блин, первый урок. И с ходу уже открыт редактор кода, а справа консоль в браузере... Так ещё и файл index.html открыт. А редактируется main.js... Не стоит ли объяснить, что вообще происходит. какой файл открываем в браузере и что в нём написано, чтоб подгружались данные из main.js??? Ааа, нашёл во вкладке "Исходный код"... Блин, ну хоть вводные можно было дать: как сайтом пользоваться... Как будто с середины уже начинаем.
name
1год назад назад
https://monsterlessons.com/project/series/javascript-dlya-nachinayushih
Woosh
8 лет назад
Добрый день, Спасибо большое за отличные курсы, подскажите пожалуйста в какой последовательности смотреть видео.
monsterlessons
8 лет назад
Добрый день. Если урок не относится к серии, то в какой угодно. Список серий вы можете посмотреть здесь https://monsterlessons.com/project/series
Woosh
8 лет назад
Спасибо!!!
Petr Pozhoga
8 лет назад
Здравствуйте Monster, есть ли смысл учить ООП на ES5 ? И для чего нужно ООП, как я понял оно применяется только в больших проектах, что бы не было путаницы и не хранить данные в переменных в массивах, одним слов не разбрасывать по всему проекту все, если это не так то для чего оно и скиньте если не трудно где применяют ООП на реальном примере.
monsterlessons
8 лет назад
Добрый день. В любом случае, если вы программист, то понимать что такое ООП и зачем оно нужно. Прототипное наследование и есть частичное применение ООП. Как вы видите в этом уроке, оно применяется для структуризации данных в классы (прототипы) и наследовании одних прототипов от других. Например, вы делает приложение с треками. Вы можете сделать прототип трека при создании инстанса, который получает какие-то данные, имеет какие-то методы, которые мы можете у него вызывать.
Zato Prosto
9 лет назад
Спасибо за видео! Заметил, что вместо YoutubeTrack.prototype = Object.create(Track.prototype); работает YoutubeTrack.prototype = Track.prototype; Корректно ли так писать? И еще. Насколько актуален сейчас такой синтаксис, с учетом существования ES6 с его классами и прочими плюшками? Понятное дело, что это обучающее видео. Просто хотелось бы знать, имеет ли смысл сейчас использовать такую нотацию? Спасибо.
monsterlessons
9 лет назад
Нет, так писать нельзя, потому что вы делаете ссылку на Track, вместо нового обьекта. Если вы используете ес6, то использовать такую нотацию смысла нет. Используйте классы и extend.