Меню Закрыть

Что такое прототипное наследование

Наследование классов в JavaScript

Наследование на уровне объектов в JavaScript, как мы видели, реализуется через ссылку __proto__ .

Теперь поговорим о наследовании на уровне классов, то есть когда объекты, создаваемые, к примеру, через new Admin , должны иметь все методы, которые есть у объектов, создаваемых через new User , и ещё какие-то свои.

Наследование 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 .

Обратите внимание, я использовал именно console.dir , а не console.log , поскольку log зачастую выводит объект в виде строки, без доступа к свойствам.

Наследование в наших классах

Применим тот же подход для наших классов: объявим класс Rabbit , который будет наследовать от Animal .

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

Для того, чтобы наследование работало, объект rabbit = new Rabbit должен использовать свойства и методы из своего прототипа Rabbit.prototype , а если их там нет, то – свойства и методы родителя, которые хранятся в Animal.prototype .

Если ещё короче – порядок поиска свойств и методов должен быть таким: rabbit -> Rabbit.prototype -> Animal.prototype , по аналогии с тем, как это сделано для объектов и массивов.

Для этого можно поставить ссылку __proto__ с Rabbit.prototype на Animal.prototype .

Можно сделать это так:

Однако, прямой доступ к __proto__ не поддерживается в IE10-, поэтому для поддержки этих браузеров мы используем функцию Object.create . Она либо встроена либо легко эмулируется во всех браузерах.

Класс Animal остаётся без изменений, а Rabbit.prototype мы будем создавать с нужным прототипом, используя Object.create :

Теперь выглядеть иерархия будет так:

В prototype по умолчанию всегда находится свойство constructor , указывающее на функцию-конструктор. В частности, Rabbit.prototype.constructor == Rabbit . Если мы рассчитываем использовать это свойство, то при замене prototype через Object.create нужно его явно сохранить:

Полный код наследования

Для наглядности – вот итоговый код с двумя классами Animal и Rabbit :

Как видно, наследование задаётся всего одной строчкой, поставленной в правильном месте.

Обратим внимание: Rabbit.prototype = Object.create(Animal.prototype) присваивается сразу после объявления конструктора, иначе он перезатрёт уже записанные в прототип методы.

В некоторых устаревших руководствах предлагают вместо Object.create(Animal.prototype) записывать в прототип new Animal , вот так:

Частично, он рабочий, поскольку иерархия прототипов будет такая же, ведь new Animal – это объект с прототипом Animal.prototype , как и Object.create(Animal.prototype) . Они в этом плане идентичны.

Но у этого подхода важный недостаток. Как правило мы не хотим создавать Animal , а хотим только унаследовать его методы!

Более того, на практике создание объекта может требовать обязательных аргументов, влиять на страницу в браузере, делать запросы к серверу и что-то ещё, чего мы хотели бы избежать. Поэтому рекомендуется использовать вариант с Object.create .

Вызов конструктора родителя

Посмотрим внимательно на конструкторы Animal и Rabbit из примеров выше:

Как видно, объект Rabbit не добавляет никакой особенной логики при создании, которой не было в Animal .

Чтобы упростить поддержку кода, имеет смысл не дублировать код конструктора Animal , а напрямую вызвать его:

Такой вызов запустит функцию Animal в контексте текущего объекта, со всеми аргументами, она выполнится и запишет в this всё, что нужно.

Здесь можно было бы использовать и Animal.call(this, name) , но apply надёжнее, так как работает с любым количеством аргументов.

Переопределение метода

Итак, Rabbit наследует Animal . Теперь если какого-то метода нет в Rabbit.prototype – он будет взят из Animal.prototype .

В Rabbit может понадобиться задать какие-то методы, которые у родителя уже есть. Например, кролики бегают не так, как остальные животные, поэтому переопределим метод run() :

Вызов rabbit.run() теперь будет брать run из своего прототипа:

Вызов метода родителя внутри своего

Более частая ситуация – когда мы хотим не просто заменить метод на свой, а взять метод родителя и расширить его. Скажем, кролик бежит так же, как и другие звери, но время от времени подпрыгивает.

Для вызова метода родителя можно обратиться к нему напрямую, взяв из прототипа:

Обратите внимание на вызов через apply и явное указание контекста.

Если вызвать просто Animal.prototype.run() , то в качестве this функция run получит Animal.prototype , а это неверно, нужен текущий объект.

Для наследования нужно, чтобы «склад методов потомка» ( Child.prototype ) наследовал от «склада метода родителей» ( Parent.prototype ).

Это можно сделать при помощи Object.create :

Для того, чтобы наследник создавался так же, как и родитель, он вызывает конструктор родителя в своём контексте, используя apply(this, arguments) , вот так:

При переопределении метода родителя в потомке, к исходному методу можно обратиться, взяв его напрямую из прототипа:

Структура наследования полностью:

Такое наследование лучше функционального стиля, так как не дублирует методы в каждом объекте.

Кроме того, есть ещё неявное, но очень важное архитектурное отличие.

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

Иначе говоря, в функциональном стиле в процессе создания Rabbit нужно обязательно вызывать Animal.apply(this, arguments) , чтобы получить методы родителя – и если этот Animal.apply кроме добавления методов говорит: «Му-у-у!», то это проблема:

…Которой нет в прототипном подходе, потому что в процессе создания new Rabbit мы вовсе не обязаны вызывать конструктор родителя. Ведь методы находятся в прототипе.

Поэтому прототипный подход стоит предпочитать функциональному как более быстрый и универсальный. А что касается красоты синтаксиса – она сильно лучше в новом стандарте ES6, которым можно пользоваться уже сейчас, если взять транслятор babeljs.

Объектно-ориентированный JavaScript: наследование

Представьте, вы получили заказ на создание небольшой онлайн игры. Всё что от вас требуется — создать понятный API для работы с персонажами. Техническое задание выглядит следующим образом:

Всего в игре три класса героев: люди, орки и эльфы. Каждый герой, независимо от его принадлежности к какому-либо классу, имеет свой запас здоровья, опыта и силы удара, а также может ходить и бегать. У каждого класса есть своя особенность: люди умеют строить небольшие сооружения для своей защиты, орки, увидев красный цвет, впадают в ярость и становятся в несколько раз сильнее, эльфы же умеют стрелять из лука и способны поразить врага на расстоянии.

Для создания подобной системы персонажей вам потребуется, как минимум, три функции конструктора: Human , Orc и Elf . Но, если вы внимательно прочитали присланное заказчиком задание, то уже знаете, что любой персонаж обладает одинаковым набором свойств и методов и лишь дополняется каким-то отдельным качеством в зависимости от принадлежности к определённому классу.

Именно в таких случаях и удобно воспользоваться преимуществами наследования. Поэтому, чтобы не повторять один и тот же код для каждого конструктора, можно создать ещё один класс персонажей, от которого и будет происходить наследование всех остальных:

Теперь мы можем создавать базовую заготовку для любого персонажа, используя модуль Character и конструктор Character :

Заготовка для всех персонажей создана, и мы можем приступать к созданию отдельных классов. Начнём с людей и модуля Human . Мы знаем, что любой персонаж, принадлежащий к классу людей, умеет строить сооружения для защиты. Для этого отлично подойдёт метод build :

Смотрите так же:  Договор дарения омск

Человек, созданный с помощью конструктора Human теперь умеет строить здания определённой прочности и тем самым увеличивать свой запас здоровья. Отлично, мы уже на полпути! Или нет? Свойства health у человека пока что нет, поэтому и вся наша конструкция бесполезна. Разумеется, мы бы могли вручную создать все необходимые свойства для каждого класса персонажей:

Но, в таком случае, нам придется дублировать код с присваиванием свойств в каждом конструкторе, поддержка кода заметно усложнится, если, например, у нас будет не 3, а 20 классов героев. Именно для упрощения поддержки и уменьшения количества кода мы и создали конструктор-заготовку. Всё, что нам остаётся сделать — вызвать конструктор Character внутри конструктора Human :

Теперь любой объект, созданный с помощью конструктора Human , обладает свойствами health , name , exp и strength . В этом легко убедиться:

Разумеется, мы можем использовать и созданный нами ранее метод build :

Методы run и walk , которые мы хотели унаследовать от конструктора Character всё ещё не доступны для использования. Чтобы разобраться, почему именно так, нужно понять принцип работы метода функций apply .

Итак, у нас есть конструктор Character , который при вызове с оператором new выдаст нам полностью рабочий объект со всеми свойствами и методами, как это было показано выше. Но не стоит забывать, что Character также является обычной функцией, то есть мы можем вызвать её и без использования оператора new :

Что произойдёт в таком случае? Если вы подобным образом используете функцию в глобальной области видимости, то глобальному объекту window будут записаны 4 свойства:

Другими словами, вы просто создадите глобальные переменные. Функция Character использует this для обращения к текущему объекту. А в глобальной области видимости this будет ссылаться на объект window . Таким образом, всё, что делает функция Character , — присваивает значения объекту, на который ссылается this при вызове функции. Именно это нам и нужно.

Неплохо было бы вызвать функцию Character в конструкторе Human , чтобы быстро записать все свойства в текущий объект. Если вы попробуете вызвать Character напрямую, то с удивлением обнаружите, что ни одно свойство не было записано в объект:

Вместо этого все четыре свойства опять были записаны в объект window . Оказалось, что this не такой умный, как мы ожидали. Но, разумеется, мы можем исправить подобную ситуацию и самостоятельно задать контекст выполнения любой функции с помощью методов call или apply . Оба метода первым аргументом принимают значение, которое будет использовано функцией в качестве this . Далее в метод call можно передать список аргументов, с которым будет вызвана функция:

Метод apply работает схожим образом, но вместо списка аргументов принимаем массив (или любую другую массивоподобную структуру) и формирует из него тот же список аргументов. Подобное поведение бывает полезным, когда мы хотим передать вызываемой функции все аргументы из текущей. Сделать это можно с помощью псевдомассива аргументов arguments :

В нашем случае предпочтительней использовать метод apply , чтобы передавать в функцию Character все аргументы, а не только объект настроек. Ведь, возможно, в будущем мы захотим доработать обе функции и добавить несколько новых аргументов.

Чтобы окончательно понять принцип работы метода apply , попробуте использовать его с любой функцией, принимающей неограниченное число аргументов. Например, функция Math.max , которая находит максимальное переданное ей число:

С помощью apply функцию можно заставить работать с массивами:

Так как функция не использует this , то первым параметром передать можно всё, что угодно:

Как я уже писал выше, мы хотим сделать так, чтобы конструктор Human не только имел все те же свойства, что и Character , но и мог использовать все методы из его прототипа: walk и run . Подобное наследование осуществить очень просто: всё, что нужно сделать — переназначить прототип конструктора Human :

Метод Object.create создаёт новый объект с указанным объектом прототипа. Таким образом мы можем использовать методы конструктора Human , когда они доступны, а в случае, если их нет, то будем обращаться уже к методам конструктора Character . Подробнее о том, как происходит определение того, какое именно свойство или метод будет использован, можно прочитать в статье о прототипах.

Таким образом для реализации наследования достаточно всего двух строчек кода:

Ещё больше наследования

Мы уже написали конструктор Human , осталось закончить работу и создать конструкторы для орков и эльфов.

Что такое прототипное наследование

Прочитав очередную умную книжку про javascript, стал разбираться в методах реализации в нём наследования. Ну, то есть всем конечно понятно, что в реальном, большом, проекте лучше всего для этого использовать функцию из какой-нибудь библиотеки( благо их много ), но ведь хочется понять, как это вообще работает.

Решил посмотреть, что на эту тему есть на хабре — в общем, лучше бы я этого не делал — кто-то пишет статьи про свои костыли на пару страниц потому что библиотечная функция чем-то не понравилась, кому-то кажется удобным доступ к предку по порядковому номеру. в общем много веселья, много кода, споров тоже достаточно а самой базы просто нет. И мне показалось что неплохо бы это исправить.

1. Итак, а в чем собственно проблема?

Проблема в том, что в javascript нет привычного наследования. Максимум что можно сделать, это создать конструктор класса:

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

Этого в большинстве случаев хватает, но иногда хочется большего.

2. А если очень хочется.

… то можно это реализовать. Классический способ выглядит так:

И его даже можно использовать:

У меня с ним, правда, есть одна маленькая проблема — обычно, для создания прототипа, я использую объектную нотацию, как было показано немного выше:

А, с этим наследованием, таким методом не очень попользуешься — или мои функции полностью затрут унаследованный прототип, или унаследованный прототип полностью затрет мои функции. К счастью, наследовать можно и по-другому.

3. Метод посвящается всем любящим объектные литералы

Он просто берет, и копирует все свойства из прототипа родителя в прототип наследника, так что теперь мой любимый объектный литерал работает как надо:

И консоль со мной согласна:

Отлично. А теперь представим гипотетическую ситуацию, когда родительских классов много, и нужно, например, вызвать какой-нибудь метод, который был перегружен 2-3 класса назад. В такой ситуации, конечно, лучше еще раз хорошенько присмотреться к архитектуре своего приложения. Теперь допустим, что там все работает, как задумано, тогда чертовски неудобно писать, например, так:

4. Но это тоже решаемо

Эта функция в прототип потомка добавляет объект с ссылкой на прототип родителя. Чтобы она заработала как надо, в прототип каждого класса нужно добавить поле

Вот, например, предположим, что в нашей иерархии появился класс BadProgrammer, которому как раз понадобился удобный доступ к прототипу далекого предка. Немного модифицируем предыдущий пример:

Класс BadProgrammer воспользовался мыслями самого первого звена нашей эволюционной цепочки классов, и, теперь, думает совсем не о том, о чём обычно думают программисты 😉

5. И еще кое что

Не представляю в какиx случаях это может пригодится, но, возможно, когда-нибудь, кому-нибудь может понадобится множественное наследование. Его тоже вполне можно реализовать:

Не очень-то и сильно отличается от предыдущей версии.
Для того, чтобы показать как оно работает, я придумал еще один вполне реалистичный пример:

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

Вот так все это и работает. Весь код и примеры положил сюда — вдруг пригодится.

UPD. Как справедливо заметил один хороший человек, во всех методах, кроме первого, instanceof отказывается признавать класс экземпляром родительского класса. При множественном наследовании этот недостаток сохранился, но для остальных случаев я его устранил.

Что такое прототипное наследование

Прошу помощи в объяснении данного текста.. Пытаюсь перейти паралельно на веб, сложно осваивается подход к Java Script после Java.

Смотрите так же:  Минимальное пособие по безработице в 2018 году

Что такое прототипное программирование ?

«это модель ООП которая не использует классы, а вместо этого сначала выполняет поведение класса» Чего .

С пониманием объектно ориентированного программирования нету проблем.

В отличие от большинства других ОО-языков (Java, C#), объектная система в JavaScript основана на прототипах, а не классах. Классы, которые вы знаете по таким языкам, как Java, технически не существуют в JavaScript (JS).

Вся иерархия объектов строиться на цепочках — прототипах. Object.prototype — объект, от которого «наследуются» все остальные объекты. Он содержит такие методы, как toString() или valueOf(). Прототип у него равен null . Замечу, что Object это просто функция-конструктор для создания объектов:

prototype , который используется в примере, применим только к функциям, а для созданных объектов используется __proto__ (или [[Prototype]] ).

Все методы массивов ( slice() , splice() ) хранятся в объекте Array.prototype , а прототип этого объекта узакывает на Object.prototype .

Получается: arr -> Array.prototype -> Object.prototype -> null

Так же с другими встроенными функциями-конструкторами, например Function , Date , Number и т.д.

Сейчас все усложнилось ещё тем, что в новом стандарте (ES6) разработчики JS ввели class – удобный «синтаксический сахар» для задания конструктора вместе с прототипом. Насколько мне известно, его ввели в том числе специально для разработчиков, которые хотят писать на JS, но которых смущает, что в них нет классов :). На самом деле class в JS это обычная функция:

Поэтому я до сих пор считаю, что понятие класс и классическое наследование немного некорректны в отношении JS.

Из плюсов прототипного наследования, наверно, это гибкость. Класс (например в Java) определяет все свойства для всех его экземпляров. Невозможно добавить свойства динамически во время выполнения. В тоже время в JS функция-конструктор определяет начальный набор свойств. Можно добавлять или удалять свойства динамически для отдельных объектов или сразу всем.

Возможно после использования интерфейсов/абстрактных классов в Java это покажется не плюсом, а минусом, но если этим уметь пользоваться, то потом этого не будет доставать в других языках.

Мы так же могли поменять реализацию только для одного объекта (сделать как нужно в рамках задачи).

Повторюсь, prototype , который используется в примере, применим только к функциям, а для созданных объектов используется __proto__ (или [[Prototype]] ). Метод max будет находится в прототипе созданных объектов ( ins1.__proto__.max ), а прототипы у них указывают на один и тот же объект:

Другие достоинства, которые можно отметить: простота, мощность данного подхода, меньше избыточного кода, динамичность.

Если интересует момент, почему прототипное наследование в JS похоже на классическое (по синтаксису, например, использование оператора new ), то это легко объяснить. Создатель JS, Brendan Eich, хотел чтобы JavaScript стал младшим братом Java и пытался его сделать максимально похожим синтаксически.

В общем я надеюсь, что вас ещё больше не запутал, просто нужно изучать изучать и ещё раз изучать 🙂

UPD.:

Пример использования «классов» из стандарта ES6 .

Все методы, объявленные в «классе» автоматически помещаются в прототип созданных объектов, а поля — в сам объект.

Ну и прототип протитипа Auto будет ссылкать на Object.prototype

Код «класса» Auto (без методов) в стандарте ES5 будет выглядеть следующим образом:

Обычная функция-конструктор с проверкой (за счет вызова _classCallCheck ), чтобы нельзя вызывать Auto как функцию. Это все же класс 🙂

Для создания методов используются Object.defineProperty , а посмотреть как работает транспайлер для методов (переводит код из ES6 в ES5 и не только) можно тут: https://babeljs.io/repl/ (Babel — один из самых популярных транспайлеров).

Классическое ООП и JS прототипы на самом деле имеют мало различий:

Любая функция в JS (кроме кратких — стрелочных функций ES6) является в терминологии ООП классом: к любой функции JS можно применять оператор new , что делает её конструктором. Это уже расширяет позицию ООП, в котором объекты конструируют не функции, а классы. Далее вместо термина функция — использую термин класс.

Прототип — это класс-предок объекта, всё как в ООП — разница только в том, что прототип в JS — это уже сконструированный «готовый» объект, а в классическом ООП «прототип» — неотделим от самого класса-потомка: то есть не является ни объектом, ни чем-либо «физическим». Как работает наследование в JS, аналогичное обычному ООП наследованию — легко понять по такому примеру:

  1. Разве что прототипы могут больше, чем могут классы — например прототип(класс-предок говоря языком ООП) можно поменять/установить как для одного объекта, так и для класса в целом во время выполнения. Например можно типу-числу (Number) добавить поведение функции (вызов через скобки), см. скрещивание ужей с ежами в MDN. Где вы видели, чтоб в ООП можно было на лету подменить класс-предок?) При операции установки прототипа — происходит тоже самое, что произошло бы при подмене базового класса: а именно все свойства/методы объекта, не найденные у себя — ищутся уже в другом классе-предке, логика изменяется. Собственно эта возможность подмены/установки базового класса — и является главной фишкой прототипного программирования.
  2. Если нужно вызывать метод класса родителя, при том что он переопределён в текущем классе: до ES6 (в котором есть super ) можно было делать так this.__proto__.someMethod.apply(this, [arg1, arg2]); . Длиннее, чем обычно в ООП — но суть остаётся той-же. Доступ к родительскому конструктору также имеется через this.__proto__.constructor.apply(this,[. ]) . Указываю на это — т.к. не очевидно поначалу — и прототипы кажутся совсем унылыми в плане ООП.
  3. ES6 и его синтаксис определения классов — это просто декорации старых добрых прототипов: то есть ничего принципиально нового в нём нет. Разве что ООП в JS стало удобнее. То есть ИМХО — Прототипное программирование это просто красное словцо. Ну да — нету protected , тоже сначала плевался, а потом понял что и не надо: инкапсуляция в JS очень красиво делается на замыканиях. Зато никто не мешает изобрести своё ООП если уж хочется(я бы не рекомендовал, но порой встечается в фреймворках), а также делать объекты мутанты — например массив может быть одновременно функцией через setPrototypeOf .

Как прототипы описаны в книжках и пособиях по JS дело другое: в ответе описана краткая помощь для разбора прототипов людям, знающим другие ООП-языки.

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

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

Классы в Javascript

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

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

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

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

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

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

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

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

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

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

Смотрите так же:  Образец заявления собственника о согласии на временную регистрацию

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Прототипы и их наследование

prototype is a property of functions and of objects that are created by constructor functions.» xml:space=»preserve»> В JavaScript prototype — это свойство функций и объектов, создаваемых функциями конструктора. Прототипом функции является объект. В основном он применяется тогда, когда функция используется в качестве конструктора.

Vehicle function is the prototype of any object that is instantiated with the Vehicle constructor.» xml:space=»preserve»> В приведенном выше примере прототип функции Vehicle является прототипом любого объекта, экземпляр которого создается с помощью конструктора Vehicle .

prototype property to add properties and methods to objects, even the ones that have already been created:» xml:space=»preserve»> Свойство prototype можно использовать для добавления свойств и методов в объекты — даже в те, которые уже созданы:

testColor is «red».» xml:space=»preserve»> Значение переменной testColor — red.

Добавлять свойства и методы можно даже в предопределенные объекты. Trim method on the String prototype object, and all the strings in your script will inherit the method.» xml:space=»preserve»> Например, можно определить метод Trim в объекте-прототипе String, и все строки будут скрипта наследовать этот метод.

prototype object can be used to derive one object from another.» xml:space=»preserve»> Объект prototype можно использовать для наследования одного объекта от другого. Object.create function to derive a new object Bicycle using the prototype of the Vehicle object we defined earlier (plus any new properties you need).» xml:space=»preserve»> Например, можно использовать функцию Object.create для наследования нового объекта Bicycle на основе ранее определенного прототипа объекта Vehicle (а также каких-либо новых необходимых свойств).

Bicycle object has the properties wheels , engine , color , and pedals , and its prototype is Vehicle.prototype.» xml:space=»preserve»> Объект Bicycle имеет свойства wheels , engine , color и pedals , и его прототипом является Vehicle.prototype. pedals property on Bicycle , and it looks up the prototype chain to find the wheels , engine , and color properties on Vehicle .» xml:space=»preserve»> Обработчик скриптов JavaScript находит свойство pedals в объекте Bicycle и просматривает цепочку прототипов, чтобы найти свойства wheels , engine и color в объекте Vehicle .

__proto__ property.» xml:space=»preserve»> В Internet Explorer 11 внутренний прототип объекта или функции можно заменить новым прототипом с помощью свойства __proto. При использовании этого свойства наследуются свойства и методы нового прототипа вместе с другими свойствами и методами в его цепочке прототипов.

В следующем примере показано изменение прототипа объекта. В нем демонстрируется изменение наследуемых свойств объекта при изменении его прототипа.

Что такое прототипное наследование

Давайте начнем с отвлеченного примера:

Это происходит потому, что объекты в JS присваиваются и передаются по ссылке а не по значению.

вы присваиваете свойству Bar.prototype ссылку на объект Foo.prototype . Как следствие, любое изменение свойства Bar.prototype приводит к изменению Foo.prototype , о чем и говорится в приведнной цитате:

This means when you start assigning, like Bar.prototype.myLabel = . you’re modifying not a separate object but the shared Foo.prototype object itself, which would affect any objects linked to Foo.prototype.

Небольшое лирическое отступление.

Вообще говоря, я бы рекомендовал вам никогда не использовать конструкцию:

а всех тех, кто вам это советует — смело отправляйте учить основы JS. Вся соль в том, что вызывая new Foo() вы вызываете конструктор объекта. При этом сам конструктор может с одной стороны накладывать ограничения на передаваемые аргументы, а с другой иметь побочные действия. Разберем каждый из этих случаев отдельно.

Предположим, у вас есть вот такой конструктор, накладывающий ограничения на свои аргументы:

В этом случае вы уже не можете просто взять и выполнить:

т.к. вам нужно в явном виде предать аргумент в конструктор, который полностью лишен смысла в момент описания иерархии наследования. Самое интересное, что значение параметра a все равно будет затерто при вызове конструктора Foo в дочернем конструкторе Bar . Поэтому конструкция new Foo() еще и лишена смысла.

Теперь предположим, что родительский конструктор имеет побочные эффекты:

строка » Here I am! » будет выведена даважды. Согласитесь, это не всегда желаемое поведение системы.

Ну и еще один любопытный факт: даже если в сейчас родительский конструктор не имеет ни побочных эффектов ни ограничений на аргументы, это не значит, что он останется таким навсегда. Лучше уж сразу сделать все правильно, чем нервно отлаживать код в поисках ошибки, когда все сломается.

Приведу, для справки, правильную реализацию наследования в JS:

В случае, когда вы не можете использовать Object.create (старые барузеры) вы можете либо использовать один из существующих полифилов, либо сделать все ручками(через анонимный конструктор):

С учетом всего выше сказанного универсальная функции наследования может иметь вид: