Какой тип наследования используется в 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 происходит через цепочки прототипов, что делает язык гибким, но требует хорошего понимания внутреннего устройства объектов.