Прототипы
Прототип — объект, от которого другой объект наследует свойства и методы. Каждый объект в JavaScript имеет внутреннюю ссылку
[[Прототипы]]на свой прототип, образуя прототипную цепочку.
Зачем нужно
Прототипы — основа наследования в JavaScript. Даже классы ES6 — синтаксический сахар над прототипами. Понимание прототипной цепочки объясняет, как работает instanceof, как методы наследуются, и почему toString доступен у любого объекта.
Где используется
Наследование, расширение встроенных объектов, полифиллы, метод-шеринг между экземплярами, проверка типов, миксины.
Предпосылки
Прототипы и proto
const animal = {
eats: true,
walk {
console.log('Животное идёт');
}
};
const rabbit = {
jumps: true,
__proto__: animal // устанавливаем прототип
};
console.log(rabbit.jumps); // true — собственное свойство
console.log(rabbit.eats); // true — унаследовано от animal
rabbit.walk; // "Животное идёт" — метод из прототипа
Object.getPrototypeOf / Object.setPrototypeOf
// Рекомендуемый способ (вместо __proto__)
const proto = { greet { return 'Привет'; } };
const obj = Object.create(proto);
console.log(Object.getPrototypeOf(obj) === proto); // true
console.log(obj.greet); // "Привет"
// Изменение прототипа (медленная операция, избегать)
Object.setPrototypeOf(obj, null);
// obj.greet; // TypeError — прототип удалён
Прототипная цепочка
const grandparent = { family: 'Ивановы' };
const parent = Object.create(grandparent);
parent.role = 'родитель';
const child = Object.create(parent);
child.name = 'Маша';
console.log(child.name); // 'Маша' — собственное
console.log(child.role); // 'родитель' — от parent
console.log(child.family); // 'Ивановы' — от grandparent
console.log(child.toString()); // function — от Object.prototype
// Цепочка: child → parent → grandparent → Object.prototype → null
Конец цепочки
console.log(Object.getPrototypeOf(Object.prototype)); // null
// Object.prototype — вершина цепочки
prototype у функций-конструкторов
function User(name) {
this.name = name;
}
// Методы в prototype — общие для всех экземпляров
User.prototype.greet = function {
return `Привет, ${this.name}`;
};
User.prototype.role = 'user';
const ivan = new User('Иван');
const maria = new User('Мария');
console.log(ivan.greet); // "Привет, Иван"
console.log(maria.greet); // "Привет, Мария"
// Один и тот же метод
console.log(ivan.greet === maria.greet); // true — экономия памяти
// Цепочка: ivan → User.prototype → Object.prototype → null
console.log(Object.getPrototypeOf(ivan) === User.prototype); // true
constructor
console.log(User.prototype.constructor === User); // true
console.log(ivan.constructor === User); // true (через цепочку)
Object.create
// Создание объекта с указанным прототипом
const personProto = {
greet {
return `Привет, я ${this.name}`;
},
toString {
return `[Person: ${this.name}]`;
}
};
const alice = Object.create(personProto);
alice.name = 'Алиса';
console.log(alice.greet); // "Привет, я Алиса"
// Создание с дескрипторами свойств
const bob = Object.create(personProto, {
name: { value: 'Боб', writable: true, enumerable: true },
age: { value: 30, writable: false }
});
// Объект без прототипа (чистый словарь)
const dict = Object.create(null);
dict.key = 'value';
console.log(dict.toString()); // undefined — нет Object.prototype
Поиск свойств в цепочке
const base = { x: 10, y: 20 };
const derived = Object.create(base);
derived.x = 100; // Затеняет base.x
console.log(derived.x); // 100 — собственное (shadow)
console.log(derived.y); // 20 — из прототипа
// Проверка: собственное свойство или унаследованное
console.log(derived.hasOwnProperty('x')); // true
console.log(derived.hasOwnProperty('y')); // false
// Оператор in проверяет и цепочку
console.log('x' in derived); // true
console.log('y' in derived); // true
// Только собственные ключи
console.log(Object.keys(derived)); // ['x']
console.log(Object.getOwnPropertyNames(derived)); // ['x']
Запись свойств
const proto = {
get value { return this._value; },
set value(v) { this._value = v; }
};
const obj = Object.create(proto);
obj.value = 42;
console.log(obj._value); // 42 — записано в obj (через setter)
console.log(obj.hasOwnProperty('_value')); // true
console.log(obj.hasOwnProperty('value')); // false — геттер/сеттер в прототипе
Перебор с учётом прототипов
const animal = { eats: true };
const rabbit = Object.create(animal);
rabbit.jumps = true;
// for...in перебирает и унаследованные
for (const key in rabbit) {
console.log(key); // jumps, eats
}
// Только собственные
for (const key in rabbit) {
if (rabbit.hasOwnProperty(key)) {
console.log(key); // только jumps
}
}
// Object.keys() — только собственные
console.log(Object.keys(rabbit)); // ['jumps']
Встроенные прототипы
// Все встроенные типы имеют прототипы
const arr = [1, 2, 3];
// arr → Array.prototype → Object.prototype → null
const str = 'hello';
// str (обёрнут) → String.prototype → Object.prototype → null
const fn = function {};
// fn → Function.prototype → Object.prototype → null
// Поэтому у массива есть map, у строки — slice и т.д.
console.log(arr.__proto__ === Array.prototype); // true
console.log(arr.__proto__.__proto__ === Object.prototype); // true
Частые ошибки
1. Перезапись prototype целиком
function User(name) { this.name = name; }
User.prototype.greet = function { return this.name; };
// Неправильно — теряем constructor и старые методы
User.prototype = {
sayHi { return 'Hi'; }
};
const user = new User('Иван');
console.log(user.constructor === User); // false!
// Правильно — добавляем метод
User.prototype.sayHi = function { return 'Hi'; };
// Или восстанавливаем constructor
User.prototype = {
constructor: User,
sayHi { return 'Hi'; }
};
2. Мутация прототипа — влияет на все экземпляры
function Item() {}
Item.prototype.tags = ; // Общий массив!
const a = new Item();
const b = new Item();
a.tags.push('test');
console.log(b.tags); // ['test'] — тоже изменился!
// Решение: инициализировать в конструкторе
function ItemFixed() {
this.tags = ; // Свой массив у каждого
}
3. Использование proto в продакшене
// __proto__ — устаревший способ, не для продакшена
// Используйте Object.create, Object.getPrototypeOf
Практика
- Создай цепочку прототипов: animal → dog → puppy и проверь наследование
- Добавь метод в
Array.prototypeи вызови его на массиве - Создай объект через
Object.create(null)— убедись, что toString отсутствует - Реализуй наследование через функции-конструкторы и prototype
- Напиши функцию
instanceOf(obj, Constructor), проверяющую цепочку прототипов
Ключевые тезисы из лекций
- «Любая функция (не лямбда) в JavaScript сразу же имеет свойство
prototype. Это пустой объект, который служит прототипом будущих инстансов» (автор, SzaXTW2qcJE). - Циклическая ссылка constructor↔prototype.
Point.prototype.constructor === Point. Это цикл, который НЕ влияет на цепочку прототипов (она идёт через__proto__). - Статика на конструкторе.
Point.from = ...— метод на функции, доступен только через имя класса. Вclassстатика помечена какnot enumerable. - Двойственная природа классов.
classпомечен внутренне какkind: class— нельзя вызвать безnew(в отличие от функции-конструктора).
Связанные темы
- Классы
- Наследование
- Конструкторы
- Инкапсуляция
- Прототипная цепочка
- Delegation Chain
- Пять способов наследования
Источники
- Timur · Прототипное программирование и наследование (2019-11-19) — главный
- Timur · Массивы, объекты, классы, прототипы в JavaScript (2018-10-04)
- Timur · Object-oriented programming (2020-02-26)
- AsForJS · JavaScript курс. Part 3 Delegation Chain (2026-04-19)
- MDN — Object prototypes
- JavaScript.info — Прототипное наследование
- JavaScript.info — F.prototype