Весной стартует сезон найма, успей отхватить свой оффер!

Прототипы и прототипное наследование в 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 — удобный способ создавать объекты, указывая прототип напрямую. Прототипное наследование даёт мощный, гибкий подход к реализации ООП без жёсткой привязки к классам.