Как работает .prototype в JavaScript? Объясните на примерах
Ключевое отличие: прототип vs конструктор
Давайте разберёмся с главным вопросом. Когда вы добавляете метод в конструктор:
function Animal(name) {
this.name = name;
this.sayHi = function() { ... };
}
Каждый раз при вызове new Animal() создаётся новая копия функции sayHi. Если у вас 1000 животных, в памяти будет 1000 одинаковых функций. Это неэффективно.
Когда вы используете .prototype:
Animal.prototype.sayHi = function() { ... };
Функция существует в единственном экземпляре на прототипе. Все объекты просто ссылаются на неё. Экономия памяти и времени.
Что такое prototype и __proto__
Это разные вещи, хотя они связаны:
.prototype— свойство функции-конструктора. Это объект, который станет прототипом для всех созданных черезnewэкземпляров.__proto__— свойство каждого объекта, которое указывает на его прототип (родителя). Это ссылка на тот самый.prototypeконструктора.
function Animal(name) {
this.name = name;
}
let cat = new Animal('Барсик');
console.log(Animal.prototype); // объект с sayHi
console.log(cat.__proto__); // тот же объект!
console.log(cat.__proto__ === Animal.prototype); // true
Цепочка прототипов
Когда вы обращаетесь к свойству объекта, JavaScript идёт по цепочке:
- Сначала проверяет сам объект
- Если не нашёл — идёт в
__proto__(прототип) - Если и там нет — в
__proto__.__proto__и так далее - Пока не дойдёт до
null(конец цепочки)
Поэтому cat.toString() работает: его нет у кота и у Animal.prototype, но он есть у Object.prototype (вершина цепочки).
// Цепочка для cat:
cat → Animal.prototype → Object.prototype → null
// Проверка:
console.log(cat.toString); // функция из Object.prototype
console.log(cat.hasOwnProperty('toString')); // false
console.log('toString' in cat); // true (нашёлся в цепочке)
Object.create() и прототипы
Object.create(proto) создаёт новый объект с указанным прототипом:
let animal = {
eat() { console.log('Ням-ням'); }
};
let rabbit = Object.create(animal);
rabbit.eat(); // "Ням-ням" — унаследовано
console.log(rabbit.__proto__ === animal); // true
Как это работает на практике
Полный пример с наследованием:
// Базовый "класс"
function Animal(name) {
this.name = name;
}
Animal.prototype.sayHi = function() {
console.log('Привет, я ' + this.name);
};
// "Наследуем"
function Cat(name, color) {
Animal.call(this, name); // вызываем родительский конструктор
this.color = color;
}
// Устанавливаем прототип
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat; // восстанавливаем constructor
// Добавляем свой метод
Cat.prototype.meow = function() {
console.log('Мяу!');
};
let barsik = new Cat('Барсик', 'рыжий');
barsik.sayHi(); // "Привет, я Барсик" — из Animal.prototype
barsik.meow(); // "Мяу!" — из Cat.prototype
console.log(barsik.color); // "рыжий" — собственное свойство
Современная альтернатива: class
С ES6 появился синтаксис class, который делает то же самое, но красивее:
class Animal {
constructor(name) {
this.name = name;
}
sayHi() {
console.log('Привет, я ' + this.name);
}
}
class Cat extends Animal {
constructor(name, color) {
super(name); // вызов родительского конструктора
this.color = color;
}
meow() {
console.log('Мяу!');
}
}
let barsik = new Cat('Барсик', 'рыжий');
Под капотом это всё те же прототипы. Просто синтаксический сахар.
<<Практический взгляд: когда прототипы реально нужны
Многие новички думают, что прототипы — это архаизм, и можно обойтись без них. На практике вы будете сталкиваться с ними постоянно, даже не осознавая.
1. Расширение встроенных объектов
Хотя это считается плохой практикой, но вы можете добавить метод всем массивам:
Array.prototype.last = function() {
return this[this.length - 1];
};
console.log([1, 2, 3].last()); // 3
2. Полифиллы (эмуляция новых возможностей в старых браузерах)
Например, если браузер не поддерживает Array.includes:
if (!Array.prototype.includes) {
Array.prototype.includes = function(element) {
return this.indexOf(element) !== -1;
};
}
3. Оптимизация производительности
Когда у вас тысячи однотипных объектов (например, частиц в игре), прототипы дают огромную экономию памяти:
function Particle(x, y) {
this.x = x;
this.y = y;
}
// Метод один для всех 10000 частиц
Particle.prototype.move = function(dx, dy) {
this.x += dx;
this.y += dy;
};
4. Современные библиотеки
React, Vue, jQuery — все используют прототипы внутри. Когда вы пишете $(selector).hide(), jQuery берёт метод из своего прототипа.
Главный совет: Если вы пишете современный код на ES6+, используйте class — он скрывает работу с прототипами, но понимать их нужно, чтобы разбираться в ошибках и читать чужой код.
Ответов пока нет.
Войдите чтобы оставить ответ