Какой тип наследования используется в JavaScript?
В JavaScript используется прототипное наследование (prototype-based inheritance), в отличие от классического наследования (class-based inheritance), применяемого в таких языках как Java, C++ и C#. Несмотря на появление синтаксиса классов с ES6, на уровне реализации JavaScript остаётся языком с прототипным наследованием. Это означает, что объекты могут наследовать свойства и методы напрямую от других объектов, без необходимости создавать классы.
🔹 Основные понятия прототипного наследования
1. Объекты и прототипы
В JavaScript каждый объект имеет скрытое свойство [[Prototype]], которое указывает на другой объект — его прототип. Этот прототип может также иметь свой прототип, и так далее, формируя прототипную цепочку (prototype chain).
Когда обращение идёт к свойству объекта, JavaScript сначала проверяет, есть ли это свойство у самого объекта. Если нет — оно ищется в прототипе, и так далее, пока:
-
Свойство найдено;
-
Или достигнут конец цепочки (null).
Пример:
const animal = {
eats: true
};
const rabbit = Object.create(animal);
rabbit.hops = true;
console.log(rabbit.eats); // true (унаследовано от animal)
console.log(rabbit.hops); // true (собственное свойство)
Здесь rabbit наследует от animal с помощью Object.create, устанавливая animal как свой прототип.
2. Прототипная цепочка
Цепочка прототипов — это механизм, по которому JavaScript ищет свойства и методы.
console.log(rabbit.\__proto__ === animal); // true
console.log(animal.\__proto_\_); // Object.prototype
console.log(Object.prototype.\__proto_\_); // null
Итоговая цепочка:
rabbit → animal → Object.prototype → null
3. Функции-конструкторы и прототипы
До ES6 классов объекты часто создавались с помощью функций-конструкторов:
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
return \`Hi, my name is ${this.name}\`;
};
const user = new Person('Alice');
console.log(user.sayHi()); // Hi, my name is Alice
-
Person.prototype — это объект, который будет прототипом для всех экземпляров Person.
-
Все созданные через new Person() объекты будут наследовать методы из Person.prototype.
4. Синтаксис классов в ES6 и выше
ES6 ввёл синтаксический сахар — class, который визуально похож на классическое ООП, но под капотом всё равно работает через прототипы.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return \`${this.name} makes a noise.\`;
}
}
class Dog extends Animal {
speak() {
return \`${this.name} barks.\`;
}
}
const dog = new Dog('Rex');
console.log(dog.speak()); // Rex barks.
Под капотом:
-
Dog.prototype наследует от Animal.prototype.
-
Механизм остаётся прототипным.
5. Наследование свойств и методов
JavaScript объекты наследуют:
-
Свойства и методы от своего прототипа.
-
Метод toString, valueOf, hasOwnProperty, isPrototypeOf и др. — от Object.prototype.
Наследование может быть расширено как вручную, так и с помощью Object.setPrototypeOf, Object.create, class и extends.
6. Инструменты управления прототипом
-
Object.create(proto) — создаёт объект с заданным прототипом.
-
Object.setPrototypeOf(obj, proto) — меняет прототип уже существующего объекта (медленно, не рекомендуется в проде).
-
Object.getPrototypeOf(obj) — возвращает текущий прототип объекта.
-
obj.__proto__ — устаревшее, но до сих пор поддерживаемое свойство для доступа к прототипу.
🔹 Отличие от классического наследования
Особенность | Прототипное наследование (JS) | Классическое наследование (Java, C++) |
---|---|---|
Основа | Объекты наследуют от объектов | Классы порождают экземпляры |
--- | --- | --- |
Механизм | Прототипная цепочка | Иерархия классов |
--- | --- | --- |
Время создания | Динамически, во время исполнения | Как правило, статически |
--- | --- | --- |
Изменение в рантайме | Возможны изменения цепочки и свойств | Более ограничено |
--- | --- | --- |
Расширение | Можно легко модифицировать прототип | Наследование фиксировано |
--- | --- | --- |
Наследование нескольких объектов | Нет (одиночное) | Нет, только через интерфейсы/абстракции |
--- | --- | --- |
🔹 Интересные особенности прототипного наследования
1. Dynamic dispatch
Если метод изменяется в прототипе, это немедленно отражается на всех объектах, унаследовавших его.
Person.prototype.sayHi = function() {
return 'Changed!';
};
console.log(user.sayHi()); // Changed!
2. Наследование встроенных объектов
Можно расширять встроенные прототипы (Array.prototype, Object.prototype), но это не рекомендуется из-за риска конфликтов.
🔹 Ограничения и подводные камни
-
Прототипное наследование сложнее для понимания начинающими;
-
Не поддерживает множественное наследование напрямую;
-
Модификация __proto__ негативно влияет на производительность;
-
Встроенные классы (например, Array, Map) имеют специфические поведения, завязанные на внутренние слоты и символы.
Таким образом, JavaScript использует прототипное наследование как основной механизм повторного использования иерархических структур. Даже классы, появившиеся в ES6, лишь упрощают работу с этим механизмом, но не изменяют его суть. Всё наследование в JavaScript происходит через цепочки прототипов, что делает язык гибким, но требует хорошего понимания внутреннего устройства объектов.