Пошук уроків, статей та іншого контенту
Як об'єкти в JavaScript успадковують властивості один від одного без класів у класичному розумінні.
На відміну від Java чи C#, де успадкування відбувається через класи, у JavaScript об'єкти успадковують безпосередньо від інших об'єктів — це називається прототипним успадкуванням (prototypal inheritance). Синтаксис class, який ми розглянемо в наступному уроці, — це зручна «обгортка» над тим самим механізмом прототипів, а не окрема система.
Коли ви звертаєтесь до властивості об'єкта, якої немає безпосередньо в ньому, рушій автоматично шукає її в прототипі цього об'єкта, потім у прототипі прототипу — і так далі, доки не знайде властивість або не дійде до кінця ланцюжка (null):
const animal = {
eats: true,
};
const rabbit = Object.create(animal); // rabbit успадковує від animal
rabbit.jumps = true;
rabbit.jumps; // true — власна властивість
rabbit.eats; // true — знайдено в прототипі (animal), не власна властивість rabbitОб'єкти, створені літералом {}, автоматично мають прототипом Object.prototype — саме звідти беруться методи на кшталт toString() чи hasOwnProperty(), які «просто працюють», хоча ви їх не визначали:
const obj = {};
obj.hasOwnProperty("test"); // false — метод узятий з Object.prototype, не власний метод objОператор in з уроку про об'єкти перевіряє весь ланцюжок прототипів, а hasOwnProperty — лише власні властивості самого об'єкта:
"eats" in rabbit; // true — знайдено в ланцюжку прототипів
rabbit.hasOwnProperty("eats"); // false — не власна властивість rabbit
rabbit.hasOwnProperty("jumps"); // true — власна властивістьДо появи синтаксису class прототипне успадкування налаштовували через функції-конструктори (правило 4 із попереднього уроку про this) і їхню властивість prototype — об'єкт, який автоматично стає прототипом кожного екземпляра, створеного через new:
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function () {
return `${this.name} видає звук`;
};
const dog = new Animal("Рекс");
dog.speak(); // "Рекс видає звук" — метод знайдено в Animal.prototype, не в dog напрямуРозміщення методів у prototype, а не безпосередньо в конструкторі, означає, що всі екземпляри Animal ділять одну функцію speak у пам'яті, а не мають кожен свою копію — важлива різниця для ефективності пам'яті при створенні багатьох екземплярів.
Сучасний код майже завжди використовує class (наступний урок) замість прямої роботи з прототипами. Але помилки на кшталт «TypeError: X is not a function» на успадкованому методі, чи розуміння того, чому зміна прототипу базового класу впливає на всі екземпляри одразу, вимагають розуміння саме цього механізму під капотом.
Плутанина between in (перевіряє весь ланцюжок) і hasOwnProperty (лише власні властивості).
Визначення методів безпосередньо в конструкторі (this.method = function () {...}) замість prototype — це створює окрему копію функції для кожного екземпляра, витрачаючи пам'ять.
Забування, що зміна властивості прототипу після створення екземплярів впливає на всі вже створені екземпляри, які успадковують саме цю властивість.
Об'єкти в JavaScript успадковують властивості й методи через ланцюжок прототипів — прихований зв'язок від об'єкта до його прототипу, і так далі до Object.prototype. Функції-конструктори з властивістю prototype — це базовий, «сирий» механізм, над яким побудований зручніший синтаксис class із наступного уроку.
Спробуйте самостійно
Створіть функцію-конструктор Book(title) з методом describe() у Book.prototype, що повертає рядок із назвою книги. Створіть два екземпляри через new і перевірте, що обидва мають доступ до describe, хоча жоден не зберігає власну копію цього методу.