Прототипы и прототипное наследование в JavaScript
Прототипное наследование — это основа объектной модели в JavaScript. В отличие от классических языков (Java, C++), где применяется классическое наследование, JavaScript реализует динамическую модель, в которой объекты могут наследовать свойства и методы друг от друга через прототип.
Что такое прототип?
Прототип — это объект, к которому обращается любой другой объект в случае, если у него нет искомого свойства или метода. Каждому объекту в JavaScript можно назначить другой объект в качестве прототипа. Если при обращении к свойству его нет в самом объекте, интерпретатор будет «подниматься» по цепочке прототипов (prototype chain), пока не найдёт нужное свойство или не достигнет объекта null
.
- childObject: объект, в котором производим поиск свойства.
- parentObject: прототип объекта childObject.
- Object.prototype: базовый прототип, из которого наследуются все объекты, если не указано иное.
- null: конец цепочки; если свойство не найдено до этого момента, результат будет undefined.
Свойство [[Prototype]] и proto
В спецификации ECMAScript у каждого объекта есть внутреннее свойство [[Prototype]]
, которое указывает на прототип. Исторически для доступа к нему многие среды (например, браузеры) предоставляют геттер/сеттер __proto__
, однако это считается устаревшим.
Современный способ — Object.getPrototypeOf
и Object.setPrototypeOf
const parent = { greet: function() { console.log("Hello!"); } };
const child = {};
// Устанавливаем parent как прототип child
Object.setPrototypeOf(child, parent);
// Теперь у child есть доступ к greet() через цепочку прототипов
child.greet(); // "Hello!"
console.log(Object.getPrototypeOf(child) === parent); // true
Object.create
Самый удобный способ создать объект с нужным прототипом — использовать метод Object.create
const person = {
sayName() {
console.log(`My name is ${this.name}`);
},
};
const john = Object.create(person);
john.name = "John";
john.sayName(); // My name is John
- person выступает «родителем» (прототипом).
- john — новый объект, чей [[Prototype]] указывает на person.
Цепочка прототипов (Prototype Chain)
Если мы вызываем свойство/метод john.sayName()
, и в john
такого свойства нет, JS будет искать это свойство в person
. Если бы в person
не было sayName
, интерпретатор пошёл бы в person.__proto__
, которым чаще всего является Object.prototype
, и так далее.
Функции-конструкторы и свойство prototype
До появления классов в ES6 (ES2015) часто использовали функции-конструкторы для имитации поведения классов.
function User(name) {
this.name = name;
}
User.prototype.sayHi = function () {
console.log(`Hi, I'm ${this.name}`);
};
const alice = new User("Alice");
const bob = new User("Bob");
alice.sayHi(); // "Hi, I'm Alice"
bob.sayHi(); // "Hi, I'm Bob"
User
— это функция-конструктор.- Свойство
User.prototype
автоматически создаётся при объявлении функции, и оно становится прототипом для всех объектов, созданных с помощьюnew User(...)
. alice
иbob
хранят ссылку на экземплярное свойство name, а методы берутся из User.prototype.
Классы и прототипное наследование
В современных версиях JS можно использовать class, который под капотом всё так же работает на прототипах:
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, I'm ${this.name}`);
}
}
const tom = new Person("Tom");
tom.greet(); // "Hello, I'm Tom"
// Проверка прототипа
console.log(Object.getPrototypeOf(tom) === Person.prototype); // true
- Компилятор создаёт функцию-конструктор
Person
и ставит ей свойствоPerson.prototype
. Все методы, определённые внутриclass Person {}
, записываются вPerson.prototype
.
Итог
- Прототип — это механизм, позволяющий одним объектам наследовать свойства и методы других объектов.
- В JS каждый объект имеет скрытое свойство [[Prototype]], которое обычно ссылается на другой объект или null.
- Цепочка прототипов позволяет искать свойство «снизу вверх»: от объекта к его прототипу, затем к прототипу прототипа, и так далее.
- Функции-конструкторы (до ES6) и классы (с ES6) используют прототипы «под капотом». Классы — просто «синтаксический сахар» над функциями-конструкторами.
- Object.create — удобный способ создавать объекты, указывая прототип напрямую. Прототипное наследование даёт мощный, гибкий подход к реализации ООП без жёсткой привязки к классам.