Кожен об'єкт має приховане посилання на інший об'єкт - прототип (Object.getPrototypeOf(obj)). Якщо властивості немає в самому об'єкті, рушій шукає її в прототипі, потім у прототипі прототипу - і так до null. Це ланцюжок прототипів.
const animal = { speak() { return `${this.name} подає голос`; } };
const dog = Object.create(animal);
dog.name = 'Рекс';
dog.speak(); // знайдено в прототипі; this - dog
Класи - синтаксис над тим самим механізмом:
class Animal {
constructor(name) { this.name = name; }
speak() { return `${this.name} подає голос`; }
}
class Dog extends Animal {
speak() { return `${super.speak()}: гав`; }
}
Під капотом Animal - функція-конструктор, методи лежать в Animal.prototype, а extends будує ланцюжок Dog.prototype → Animal.prototype → Object.prototype. Методи не копіюються в кожен екземпляр - усі екземпляри ділять одні й ті самі методи через прототип.
Що відрізняє класи від «ручних» конструкторів:
- клас не можна викликати без
new; - тіло класу завжди в строгому режимі;
- приватні поля
#secret- справді приватні, на рівні мови, а не за домовленістю; - поля класу (
count = 0) створюються на кожному екземплярі, а не в прототипі.
Пастки:
- Змінюваний об'єкт у прототипі спільний для всіх екземплярів: масив, доданий у прототип, - один на всіх.
- Розширювати вбудовані прототипи (
Array.prototype.myMethod = ...) погано: конфлікти з бібліотеками й майбутніми версіями мови.
Докладніше в документації: Успадкування і ланцюжок прототипів