Прототипная цепочка

Прототипная цепочка — механизм наследования в JavaScript: каждый объект имеет внутреннюю ссылку [[Prototype Pattern]] на другой объект (прототип), и при поиске свойства движок проходит по всей цепочке до null.

Зачем нужно

Прототипная цепочка — основа всего наследования в JavaScript. Понимание этого механизма объясняет, откуда у любого объекта берутся методы toString, hasOwnProperty, valueOf, как работает extends у классов и почему изменение прототипа влияет на все экземпляры.

Где используется

  • Наследование методов в классах (ES6+) и функциях-конструкторах
  • Расширение встроенных типов (не рекомендуется, но важно понимать)
  • Паттерны: Mixin, Delegation, Object.create-based inheritance
  • Понимание instanceof и hasOwnProperty

Как работает поиск свойства

const animal = {
  breathe { return 'дышит'; }
};

const dog = Object.create(animal); // dog.[[Prototype Pattern]] = animal
dog.bark = function { return 'Гав!'; };

const rex = Object.create(dog); // rex.[[Prototype Pattern]] = dog
rex.name = 'Рекс';

// Поиск rex.bark:
// 1. Есть ли у rex? → нет
// 2. Есть ли у dog (прототип rex)? → да → возвращаем
console.log(rex.bark); // 'Гав!'

// Поиск rex.breathe:
// 1. rex → нет
// 2. dog → нет
// 3. animal → да → возвращаем
console.log(rex.breathe); // 'дышит'

// Поиск rex.unknown:
// ... проходим всю цепочку до null → undefined
console.log(rex.unknown); // undefined

Цепочка в диаграмме

rex  →  dog  →  animal  →  Object.prototype  →  null
name    bark    breathe     toString, hasOwnProperty...

Object.create и явная цепочка

const vehicle = {
  type: 'транспорт',
  move { return `${this.type} движется`; }
};

const car = Object.create(vehicle);
car.type = 'автомобиль';
car.honk = function { return 'Би-би!'; };

const tesla = Object.create(car);
tesla.type = 'электромобиль';

console.log(tesla.move); // 'электромобиль движется'
console.log(tesla.honk); // 'Би-би!'

// Проверка цепочки
console.log(Object.getPrototypeOf(tesla) === car);      // true
console.log(Object.getPrototypeOf(car) === vehicle);    // true

Прототип и классы

ES6-классы — синтаксический сахар над прототипной цепочкой:

class Animal {
  constructor(name) { this.name = name; }
  speak { return `${this.name} издаёт звук`; }
}

class Dog extends Animal {
  speak { return `${this.name} лает`; }
  fetch { return `${this.name} приносит мяч`; }
}

const rex = new Dog('Рекс');

// Методы живут на прототипе, не на экземпляре
console.log(rex.hasOwnProperty('name'));   // true — собственное
console.log(rex.hasOwnProperty('speak'));  // false — на прототипе

console.log(Object.getPrototypeOf(rex) === Dog.prototype);    // true
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true

Изменение прототипа влияет на все экземпляры

function Person(name) { this.name = name; }

const ivan = new Person('Иван');
const anna = new Person('Анна');

// Добавляем метод на прототип — доступен всем экземплярам
Person.prototype.greet = function {
  return `Привет, я ${this.name}`;
};

console.log(ivan.greet); // 'Привет, я Иван'
console.log(anna.greet); // 'Привет, я Анна'
// Метод не копируется в каждый экземпляр — только ссылка через [[Prototype Pattern]]

Частые ошибки

1. Прямое присвоение __proto__ в продакшне

// Устаревший синтаксис — не используй
obj.__proto__ = proto;

// Правильно:
const obj = Object.create(proto);
// или
Object.setPrototypeOf(obj, proto); // медленнее, нарушает оптимизацию V8

2. Расширение Object.prototype — загрязняет глобально

Object.prototype.myMethod = function {}; // опасно!
// Появится в каждом for...in, ломает чужой код

3. Создание объекта с null-прототипом

const plain = Object.create(null); // нет [[Prototype Pattern]]
plain.hasOwnProperty() // undefined — метод недоступен!
// Используй Object.prototype.hasOwnProperty().call(plain, 'key')

Цепочка — это частный случай Delegation Chain

Поиск свойства, разрешение идентификаторов, цепочка scope — всё построено на одном универсальном принципе Delegation Chain: единственная связь к следующему звену, никаких колец.

«Один принцип объясняет сразу пять механизмов JavaScript: prototype chain, разрешение идентификаторов, окружения, поведение генераторов и работу Object» — автор, Part 3.

Производительность: длина цепочки

«Уход вглубь — это лишнее время. Если свойство в конце цепочки, поиск долгий. При промахе движок проходит до самого null».

Из-за этого глубокие иерархии наследования замедляют не только модификацию, но и чтение методов. См. Антипаттерны ООП.

Связанные темы

Источники