Открыть сервис

Прототипное наследование

Прототипное наследование — это механизм в объектно-ориентированном программировании, при котором объект может наследовать свойства и методы другого объекта, выступающего в роли прототипа. В отличие от классического наследования, основанного на классах, прототипное наследование не требует явного определения иерархии классов; объекты создаются напрямую, а связь между ними устанавливается через ссылку на прототип. Если запрашиваемое свойство отсутствует у самого объекта, поиск продолжается по цепочке прототипов (prototype chain) до тех пор, пока свойство не будет найдено или цепочка не закончится (обычно на null).

История и происхождение

Концепция прототипного наследования была впервые реализована в языке программирования Self, разработанном в 1986–1990 годах в исследовательском центре Xerox PARC (Дэвид Унгер и Рэндалл Смит). Self был создан как экспериментальный язык для изучения объектно-ориентированных моделей, альтернативных классам. Вместо классов Self оперировал только объектами и прототипами, что позволяло динамически изменять поведение объектов во время выполнения.

Идеи Self оказали значительное влияние на JavaScript, который был создан Бренданом Эйхом в 1995 году. Изначально язык проектировался для браузера Netscape Navigator и должен был быть простым и гибким. Эйх заимствовал из Self механизм прототипов, но адаптировал его для синтаксиса, похожего на Java и C. С тех пор прототипное наследование стало фундаментальной особенностью JavaScript, а также ряда других языков, таких как Lua, Io, NewtonScript и Scheme (с расширениями).

Основные принципы

Объекты и прототипы

В прототипной модели каждый объект имеет внутреннюю ссылку на другой объект — свой прототип. В JavaScript эта ссылка обычно обозначается как [[Prototype]] (доступна через свойство __proto__ или методы Object.getPrototypeOf() и Object.setPrototypeOf()). Прототип может быть любым объектом, включая null.

Основные правила:

  • Свойства и методы, определённые у прототипа, доступны всем объектам, которые на него ссылаются.
  • Если объекту требуется свойство, которого у него нет, интерпретатор ищет его в прототипе, затем в прототипе прототипа и так далее.
  • При присваивании значения свойству объекта (если оно уже существует) оно изменяется только у самого объекта, а не у прототипа. При создании нового свойства оно также добавляется только объекту.

Цепочка прототипов

Цепочка прототипов (prototype chain) — это последовательность объектов, соединённых ссылками [[Prototype]]. Она заканчивается, когда встречается объект с прототипом null. В JavaScript конечным звеном обычно является Object.prototype, прототипом которого является null.

Пример цепочки для массива: `` [1, 2, 3] → Array.prototype → Object.prototype → null ``

Создание объектов с прототипом

В JavaScript существует несколько способов создания объектов с заданным прототипом:

  • Литеральная нотация: const obj = { key: 'value' } создаёт объект с прототипом Object.prototype.
  • Функция Object.create(proto): создаёт новый объект, устанавливая proto в качестве его прототипа.
  • Конструкторы и оператор new: при вызове функции-конструктора с new создаётся объект, чей прототип устанавливается на Constructor.prototype.
  • Классы (синтаксический сахар): начиная с ES6, классы в JavaScript используют тот же механизм прототипов, но скрывают его за синтаксисом, похожим на классическое наследование.

Сравнение с классическим наследованием

ХарактеристикаПрототипное наследованиеКлассическое наследование
ОсноваОбъекты и прототипыКлассы и экземпляры
Создание объектаКопирование или клонирование прототипаИнстанцирование класса
Изменение поведенияДинамическое (можно изменить прототип в рантайме)Статическое (класс обычно фиксирован)
ИерархияЦепочка прототиповДерево классов
Примеры языковJavaScript, Lua, SelfJava, C++, C#

Примеры реализации

JavaScript

``javascript // Создание прототипа const animal = { type: 'unknown', speak() { console.log(${this.type} издает звук`); } };

// Создание объекта с прототипом animal const dog = Object.create(animal); dog.type = 'Собака'; dog.speak(); // "Собака издает звук"

// Проверка цепочки прототипов console.log(dog.__proto__ === animal); // true console.log(animal.isPrototypeOf(dog)); // true ```

Lua

В Lua прототипное наследование реализуется через метатаблицы и поле __index:

```lua -- Создание прототипа local animal = { type = 'unknown' } function animal:speak() print(self.type .. ' издает звук') end

-- Создание объекта с прототипом local dog = { type = 'Собака' } setmetatable(dog, { __index = animal }) dog:speak() -- "Собака издает звук" ```

Преимущества и недостатки

Преимущества

  • Гибкость: можно динамически изменять прототипы объектов во время выполнения, добавляя или заменяя методы.
  • Экономия памяти: методы и общие свойства хранятся в одном прототипе, а не копируются в каждый объект.
  • Простота модели: нет необходимости в сложной иерархии классов; объекты создаются напрямую.

Недостатки

  • Производительность: поиск по цепочке прототипов может быть медленнее, чем прямой доступ к свойству, особенно при длинных цепочках.
  • Сложность отладки: неявное наследование может затруднить понимание того, откуда берётся то или иное свойство.
  • Отсутствие строгой типизации: в языках с прототипным наследованием часто сложнее реализовать статическую проверку типов.

Применение

Прототипное наследование широко используется в веб-разработке на JavaScript, где оно лежит в основе DOM-объектов, функций-конструкторов и классов ES6. В Node.js и браузерных средах прототипы активно применяются для организации кода, создания плагинов и расширения функциональности встроенных объектов.

В игровых движках и симуляторах (например, на Lua) прототипное наследование позволяет эффективно создавать множество объектов с общим поведением, экономя память.

Интересные факты

  • В JavaScript свойство __proto__ не является стандартным, но поддерживается большинством современных браузеров. Официальный способ доступа — Object.getPrototypeOf().
  • В языке Self прототипы могли быть «родителями» и «детьми» одновременно, что позволяло строить сложные динамические иерархии.
  • Прототипное наследование часто называют «делегирующим», так как объект делегирует поиск свойств своему прототипу.

Источники

  • Унгер Д., Смит Р. «Self: The Power of Simplicity» (1987)
  • Эйх Б. «JavaScript: The First 20 Years» (2020)
  • Спецификация ECMAScript (ECMA-262), раздел «Object Internal Methods and Internal Slots»
  • Фланаган Д. «JavaScript: The Definitive Guide» (7-е издание, 2020)
  • Документация Lua 5.4: «Metatables and Metamethods»

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →