Спадкування класів у JavaScript
Тепер поговоримо про спадкування на рівні класів, тобто коли об'єкти, створювані, наприклад, через new Admin, повинні мати всі методи, які є у об'єктів, що створюються через new User, і ще якісь свої.
Спадкування Array від Object
Погляньмо на нього ще раз на прикладі Array, який успадковує від Object:

- Методи масивів Array зберігаються в Array.prototype.
- Array.prototype має прототипом Object.prototype.
Тому коли екземпляри класу Array хочуть отримати метод масиву – вони беруть його зі свого прототипу, наприклад Array.prototype.slice.
Якщо ж потрібний метод об'єкта, наприклад, маєOwnProperty , то його в 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 за умовчанням завжди знаходиться властивість конструктора, що вказує на функцію-конструктор. Зокрема, Rabbit.prototype.constructor == Rabbit . Якщо ми розраховуємо використовувати цю властивість, то при заміні prototype через Object.create потрібно її явно зберегти:
Повний код успадкування
Для наочності - ось підсумковий код з двома класами Animal і Rabbit:
Як видно, успадкування задається лише одним рядком, поставленим у правильному місці.
У деяких застарілих посібниках пропонують замість Object.create(Animal.prototype) записувати в прототип new Animal, ось так:
Частково він робітник, оскільки ієрархія прототипів буде така ж, адже new Animal – це об'єкт з прототипом Animal.prototype, як і Object.create(Animal.prototype). Вони у цьому плані ідентичні.
Але цей підхід має важливий недолік. Як правило ми не хочемо створювати Animal, а хочемо лише успадкувати його методи!
Більше того, на практиці створення об'єкта може вимагати обов'язкових аргументів, впливати на сторінку в браузері, робити запити до сервера і ще щось, чого ми хотіли б уникнути.Тому рекомендується використовувати варіант з Object.create.
Виклик конструктора батька
Подивимося уважно на конструктори Animal та Rabbit із прикладів вище:
Як видно, об'єкт Rabbit не додає жодної особливої логіки при створенні, якої не було в Animal.
Щоб спростити підтримку коду, є сенс не дублювати код конструктора Animal, а безпосередньо викликати його:
Такий виклик запустить функцію Animal в контексті поточного об'єкта, з усіма аргументами, вона виконається і запише в це все, що потрібно.
Тут можна було б використовувати і Animal.call(this, name), але apply надійніше, оскільки працює з будь-якою кількістю аргументів.
Перевизначення методу
Отже, Rabbit успадковує Animal. Тепер якщо якогось методу немає в Rabbit.prototype - він буде взятий з Animal.prototype.
У Rabbit може знадобитися задати якісь методи, які батьки вже мають. Наприклад, кролики бігають не так, як решта тварин, тому перевизначимо метод run() :
Виклик rabbit.run() тепер братиме run зі свого прототипу:

Виклик методу батька всередині свого
Частіша ситуація - коли ми хочемо не просто замінити метод на свій, а взяти метод батька і розширити його. Скажімо, кролик біжить так само, як інші звірі, але час від часу підстрибує.
Для виклику методу батька можна звернутися безпосередньо до нього, взявши з прототипу:
Зверніть увагу на виклик через apply та явну вказівку контексту.
Якщо викликати просто Animal.prototype.run() , то як ця функція run отримає Animal.prototype , а це невірно, потрібен поточний об'єкт.
Для успадкування потрібно, щоб "склад методів нащадка" (Child.prototype) успадковував від "складу методу батьків" (Parent.prototype).
Це можна зробити за допомогою Object.create:
Для того, щоб спадкоємець створювався так само, як і батько, він викликає конструктор батька у своєму контексті, використовуючи apply (this, arguments), ось так:
При перевизначенні методу батька нащадку, до вихідного методу можна звернутися, взявши його з прототипу:
Структура успадкування повністю:
Таке успадкування краще функціонального стилю, оскільки не дублює методи у кожному об'єкті.
Крім того, є ще неявна, але дуже важлива архітектурна відмінність.
Найчастіше виклик конструктора має якісь побічні ефекти, наприклад, впливає на документ. Якщо конструктор батька має якусь поведінку, яку потрібно перевизначити у нащадку, то у функціональному стилі це неможливо.
Інакше кажучи, у функціональному стилі в процесі створення Rabbit потрібно обов'язково викликати Animal.apply(this, arguments) , щоб отримати методи батька – і якщо цей Animal.apply, окрім додавання методів, каже: «Му-у-у!», то це проблема :
…Як немає в прототипному підході, тому що в процесі створення new Rabbit ми зовсім не зобов'язані викликати конструктор батька. Адже методи перебувають у прототипі.
Тому прототипний підхід варто віддавати перевагу функціональному як швидший і універсальніший. А що стосується краси синтаксису - вона дуже краща в новому стандарті ES6, яким можна користуватися вже зараз, якщо взяти транслятор babeljs.