Ви, мабуть, вже чули щось на зразок: «У JavaScript усе є об'єктом».
Якщо ви ще не мали справ з об'єктами та не знаєте, які вони на вигляд, ось приклад:

JavaScript керується принципами прототипного об'єктноорієнтованого програмування, а не класового. Саме тому наведене на початку статті твердження здобуло таку популярність.
У JavaScript існує два основних типи значень:
- Примітиви: рядки, числа, булеві значення, undefined та null.
- Об'єкти: масиви, функції, дати.
Отже, ми з'ясували, що у JavaScript не все є об'єктом. Однак необ'єктні речі огортаються в об'єкти під час компіляції, дозволяючи JavaScript здійснювати справді складні операції. Навіть якщо ви думаєте, що значення у вашому коді не є об'єктом, то, найімовірніше, JavaScript вже огорнув його.
Наприклад, якщо ми створимо порожню функцію під назвою hero
:
function hero() {}
Як вже зазначалось, функції також розглядаються у JavaScript як об'єкти. Якщо ми додамо властивість type
до цієї функції та виведемо результат виконання, то побачимо, що функція hero
є об'єктом.
function hero() {}
hero.type = 'superman'
console.log(hero)
//Результат
{ [Function: hero] type: 'superman' }
Це одна з багатьох причин, чому JavaScript такий чудовий 🙌.
Аби зрозуміти, що таке Прототипне Наслідування, найперше треба розібратись, що таке об'єктноорієнтована мова програмування.
Флешбек — Об'єктноорієнтоване програмування
В ООП об'єкти використовують методи та властивості для взаємодії один з одним та створення складних застосунків. Такий підхід дозволяє розробникам з легкістю зберігати дані у структурованому та чистому вигляді.
У наведеному вище прикладі ми лише зберігали деякі дані. Якщо я хочу створити інший об'єкт «Batman», доведеться створювати усе спочатку:

У реальних застосунках такий підхід призводить до надлишкової повторюваності, це втомлює.
Можна побачити, що обидва об'єкти зберігають однакові дані: name
, alias
та planet
. А якщо ми створимо загальний об'єкт, який використаємо для створення його окремих екземплярів?
Якщо ви знайомі з іншими мовами програмування, то вже здогадалися, що я маю на увазі створення класу. Але JavaScript базується на прототипах, а не класах.
У JavaScript загальний об'єкт відомий як Конструктор або Прототип.
JavaScript розглядає звичайну функцію та конструктор як одне й те саме. Але щоб розробникам було легше розрізняти їх, назви конструкторів пишуть з великої літери.
Прототип дозволяє створювати його унікальні екземпляри. Такий підхід використовується для чистоти та лаконічності коду.
Базуючись на вже створених об'єктах, я можу створити Прототип. Почнемо зі створення нової функції Hero
. Важливо, щоб перша літера назви була великою.

Тепер я можу використовувати прототип для створення об'єктів superman
та batman
, застосовуючи ключове слово new
. Ми використовуємо прототипну функцію для «конструювання» нового об'єкту — саме тому прототипи відомі також як конструктори.

Наслідування
Наслідування — процес, що дозволяє одному об'єкту базуватися на іншому. Такий підхід дозволяє об'єктам розділяти спільні властивості.
Раніше ми вже створили прототип Hero
та використали його для створення нового об'єкта superman
. Але ми не робили нічого з цим об'єктом. Тому подбаємо про це, створивши іншу функцію під назвою dialogue
.
function dialogue() {
console.log('I am ' + this.name);
}
Якщо ми запустимо наш код зараз, нічого не трапиться — тому що функція не знає, яким насправді є значення поля name
.
Я хочу, щоб нова функція мала такі ж властивості, як і Hero
. Замість прописувати їх у тілі функції, ми можемо просто вказати JavaScript, що необхідно наслідувати згадані властивості з прототипу Hero
.
Згадане цілком можливо втілити за допомогою властивості prototype
, що доступна у будь-якому JavaScript-об'єкті.

Розмістивши dialogue
після Hero.prototype
, ми зробили її доступною для усіх екземплярів Hero
.
Диференційоване наслідування
JavaScript також підтримує іншу модель наслідування — диференційоване наслідування. У цій моделі методи не копіюються від «батька» до «нащадка». Натомість існує посилання між батьківським та дочірнім об'єктом.
superman
насправді не має власного методу під назвою dialogue()
. Але як тоді спрацьовує superman.dialogue()
?
Коли рушій JavaScript знаходить у коді рядок superman.dialogue()
, він шукає властивість dialogue
всередині об'єкта superman
. Коли він не бачить таку, то починає шукати прототип, що пов'язує об'єкт superman
з його батьківським прототипом Hero.prototype
. Тоді він знаходить Hero.prototype.dialogue
. Далі здійснюється виклик знайденої функції через this
, що прив'язує функцію до superman
.
Object.create()
Все можна зробити більш зрозуміло, якщо створити новий клас для Superman
, який успадкує властивості прототипу Hero
. Такого ефекту можна досягнути присвоєнням прототипу Superman
до прототипу Hero
, ось так:
function Superman() {}
Superman.prototype = Hero.prototype
Але цим виразом ми лише прирівняли Superman
та Hero
. Нам необхідно створити новий об'єкт, що базується на прототипі Hero
. З ES5 у JavaScript з'явилася вбудована функція Object.create()
. Використаємо її так:
Superman.prototype = Object.create(Hero.prototype);
Створюємо новий порожній об'єкт, що базується на прототипі Hero
, та присвоюємо його прототипу Superman
. Так усі властивості, які ми мали у прототипі Hero
, тепер можна отримати у прототипі Superman
. Тому замість виклику new Hero
, ми можемо викликати new Superman
— і все працюватиме як слід.
Але якщо ви подивитесь на результат ближче, то помітите, що там є undefined
. Так виходить, тому що наразі Hero
є конструктором лише для самого себе. Нам треба здійснити call
властивостей прототипу Hero
всередині прототипу Superman
.
function Superman() {
Hero.call(this, 'Superman', 'Clark Kent', 'Krypton')
}
Створимо інший конструктор під назвою MarvelMovies
:
function MarvelMovies(movieName, releaseYear) {
this.movieName = movieName;
this.releaseYear = releaseYear;
}
Коли функція використовується у конструкторі, this
стосується нового об'єкта, що створюється. Тому в нашому конструкторі ми приймаємо аргументи movieName
й releaseYear
та присвоюємо їхні значення властивостям movieName
та releaseYear
нового екземпляру MarvelMovies
— avengers
.
var avengers = new MarvelMovies("avengers", 2012);
Створимо новий метод під назвою output
для прототипу:
MarvelMovies.prototype.output = function() {
return "Movie: " + this.movieName + " Released in " + this.releaseYear;
}
console.log(avengers.output());
Майбутнє наслідування
Дійсно чудовим аспектом наслідування є те, що JavaScript дозволяє модифікувати чи розширяти функції класу навіть після його оголошення.
JavaScript відшукає прототип при спробі доступу до властивостей об'єкта. Тож можна змінювати класи у рантаймі.
Для наочності створимо масив:
var numbers = [11, 22, 33, 44, 55];
Array.prototype.shuffle = function() {
return this.sort(function() {
return Math.round( Math.random() * 2) - 1;
});
};
console.log(numbers.shuffle());
Тут масив numbers
існував до оголошення Array.prototype.shuffle
. Але у JavaScript пошук властивостей йде прототипним ланцюжком. Нам вдається використати метод shuffle
до масиву саме тому, що він є у ланцюжку Array.prototype
.
Простіше кажучи, ми створили масив, а потім надали усім масивам доступ до нового методу.
Висновок
Існує багато ресурсів, що пояснюють принцип роботи прототипного наслідування у JavaScript. В офіційній документації JavaScript зазначено, що:
- Наслідування у JavaScript реалізується єдиною конструкцією:
objects
. Коженobject
має внутрішнє посилання на іншийobject
, що є його прототипом (prototype
). Цей прототипний об'єкт має прототип самого себе і так далі, поки не буде досягнуто null.null
за визначенням не має прототипу та поводиться як фінальне посилання у ланцюжку прототипів.*
Таке визначення може здатися заплутаним та важким для розуміння. Саме тому більшість розробників переходять на класове наслідування замість прототипного.
Існує ще багато цікавого у прототипному наслідуванні. З кожним новим синтаксисом, що надається ES5, ES6, ES7, з'являються нові можливості. Наприклад, object.assign
є чудовим способом удосконалити фабричні методи та водночас звести код до мінімуму. Декілька цікавих способів використання має також Object.create
.
Ще немає коментарів