Прототипное наследование
Прототипное наследование — это механизм в объектно-ориентированном программировании, при котором объект может наследовать свойства и методы другого объекта, выступающего в роли прототипа. В отличие от классического наследования, основанного на классах, прототипное наследование не требует явного определения иерархии классов; объекты создаются напрямую, а связь между ними устанавливается через ссылку на прототип. Если запрашиваемое свойство отсутствует у самого объекта, поиск продолжается по цепочке прототипов (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, Self | Java, 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 →