Прототипы и наследование: prototype chain, Object.create()
Прототипное наследование: магия JavaScript под капотом
JavaScript — единственный популярный язык, где наследование реализовано через прототипы, а не классы (да, class — это синтаксический сахар!). Давайте разберёмся, как это работает на самом деле.
Каждый объект в JS имеет скрытое свойство [[Prototype]] (не путать с prototype у функций!). Когда мы пытаемся получить свойство объекта, движок сначала ищет его в самом объекте, а если не находит — идёт по цепочке прототипов.
const animal = { eats: true };
const rabbit = { jumps: true };
rabbit.__proto__ = animal; // Устаревший способ! Современный аналог — Object.setPrototypeOf()
console.log(rabbit.eats); // true (из прототипа)
console.log(rabbit.jumps); // true (собственное свойство)
💡
__proto__— устаревший синтаксис! В современных проектах используйтеObject.getPrototypeOf()иObject.setPrototypeOf().
Как работает prototype у функций-конструкторов
Когда мы создаём функцию, у неё автоматически появляется свойство prototype — объект с единственным полем constructor, которое ссылается обратно на функцию.
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function() {
console.log(`${this.name} says woof!`);
};
const rex = new Dog('Rex');
rex.bark(); // Rex says woof!
Что здесь происходит?
new Dog()создаёт пустой объект, у которого[[Prototype]] = Dog.prototype.thisвнутри конструктора ссылается на этот объект.- Если функция не возвращает свой объект, движок возвращает
this.
Цепочка прототипов в действии
Поиск свойства идёт вверх по цепочке до первого совпадения или до null (конца цепочки).
function Animal() {}
Animal.prototype.eats = true;
function Rabbit() {}
Rabbit.prototype = Object.create(Animal.prototype); // Наследование!
const fluffy = new Rabbit();
console.log(fluffy.eats); // true (из Animal.prototype)
Визуализация цепочки:
fluffy → Rabbit.prototype → Animal.prototype → Object.prototype → null
Современный способ: Object.create()
Метод Object.create() создаёт новый объект с указанным прототипом — это чище, чем __proto__.
const animal = { eats: true };
const rabbit = Object.create(animal, {
jumps: { value: true, enumerable: true }
});
console.log(rabbit.eats); // true (из прототипа)
console.log(Object.getPrototypeOf(rabbit) === animal); // true
Плюсы подхода:
✔️ Не требует функций-конструкторов
✔️ Позволяет точно настраивать свойства (enumerable/configurable)
✔️ Более явное наследование
Когда прототипы вступают в игру
1. Добавление методов всем экземплярам:
Array.prototype.customMap = function(callback) {
const result = [];
for (let i = 0; i < this.length; i++) {
result.push(callback(this[i], i, this));
}
return result;
};
[1, 2, 3].customMap(x => x * 2); // [2, 4, 6]
2. Оптимизация памяти: методы в прототипе создаются один раз, а не копируются для каждого объекта.
Осторожно: подводные камни
1. Изменение прототипа в рантайме — плохая практика:
// Так делать не стоит!
String.prototype.reverse = function() {
return this.split('').reverse().join('');
};
2. Циклы for..in включают унаследованные свойства:
const obj = { a: 1 };
const child = Object.create(obj, { b: { value: 2 } });
for (let key in child) {
console.log(key); // 'a', 'b' (если enumerable)
}
Используйте hasOwnProperty() для фильтрации:
for (let key in child) {
if (child.hasOwnProperty(key)) {
console.log(key); // только 'b'
}
}
Проверь себя: мини-тест
1. Как создать объект без прототипа?
Ответ:
const obj = Object.create(null);
2. Что выведет код?
function A() {}
function B() {}
A.prototype = B.prototype = { test: 1 };
console.log(new A().test === new B().test);
Ответ:
true(оба экземпляра используют один прототип)
Подведём итоги
🔹 Прототипы — основа наследования в JavaScript
🔹 Object.create() — современная замена __proto__
🔹 Функции-конструкторы используют prototype для наследования методов
🔹 Цепочка прототипов обходится до первого совпадения
Главный секрет: Классы в JS — это просто функции-конструкторы с синтаксическим сахаром! 🍬
Попробуйте создать свою иерархию объектов через прототипы — это лучший способ понять механизм изнутри!