В интернет-магазине тысячи объектов-товаров. У каждого есть методы getPrice(), formatName(), isAvailable(). Если создавать эти методы внутри конструктора — каждый объект получит свою копию функции. Тысяча товаров = тысяча одинаковых функций в памяти. Прототипы решают это: метод существует в одном экземпляре, а все объекты его разделяют.
Каждый объект в JavaScript имеет скрытую ссылку [[Prototype]] на другой объект — свой прототип. При обращении к свойству объекта JS ищет его сначала в самом объекте, потом в прототипе, потом в прототипе прототипа — и так до null. Это цепочка прототипов.
const animal = { eats: true, breathes: true }
const dog = { barks: true }
// Устанавливаем прототип: dog[[Prototype]] = animal
Object.setPrototypeOf(dog, animal)
console.log(dog.barks) // true — собственное свойство
console.log(dog.eats) // true — найдено в прототипе animal
console.log(dog.flies) // undefined — нет нигде в цепочке
// Цепочка: dog → animal → Object.prototype → nullconst vehicleProto = {
describe() {
return `${this.brand} ${this.model} (${this.year})`
},
isNew() {
return new Date().getFullYear() - this.year < 3
}
}
const car = Object.create(vehicleProto)
car.brand = 'Toyota'
car.model = 'Camry'
car.year = 2022
console.log(car.describe()) // 'Toyota Camry (2022)'
console.log(Object.getPrototypeOf(car) === vehicleProto) // trueМетоды в Constructor.prototype разделяются всеми экземплярами — один объект функции в памяти:
function Product(name, price) {
// собственные свойства — уникальны для каждого экземпляра
this.name = name
this.price = price
}
// Методы — в прототипе — один раз для всех
Product.prototype.getPrice = function() {
return `${this.price} ₽`
}
Product.prototype.isExpensive = function() {
return this.price > 10000
}
const laptop = new Product('Ноутбук', 75000)
const mouse = new Product('Мышь', 1500)
console.log(laptop.getPrice()) // '75000 ₽'
console.log(mouse.isExpensive()) // false
// Один объект метода разделяется всеми экземплярами:
console.log(laptop.getPrice === mouse.getPrice) // true — одна функция!function Animal(name) {
this.name = name
}
Animal.prototype.speak = function() {
return `${this.name} издаёт звук`
}
function Dog(name, breed) {
Animal.call(this, name) // вызов родительского конструктора
this.breed = breed
}
// Dog.prototype наследует от Animal.prototype
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog // восстанавливаем constructor
// Переопределяем / добавляем методы
Dog.prototype.speak = function() {
return `${this.name} лает: Гав!`
}
Dog.prototype.fetch = function() {
return `${this.name} приносит мяч`
}
const rex = new Dog('Рекс', 'Лабрадор')
console.log(rex.speak()) // 'Рекс лает: Гав!'
console.log(rex.fetch()) // 'Рекс приносит мяч'
console.log(rex instanceof Dog) // true
console.log(rex instanceof Animal) // true — цепочка работаетconsole.log(rex.hasOwnProperty('name')) // true — собственное
console.log(rex.hasOwnProperty('speak')) // false — из прототипа
// В цикле for...in — свои + прототипные. Фильтруй если нужно:
for (const key in rex) {
if (rex.hasOwnProperty(key)) {
console.log(key, rex[key]) // только name и breed
}
}Ошибка 1: забыть восстановить constructor
// Сломано:
Dog.prototype = Object.create(Animal.prototype)
// Dog.prototype.constructor теперь Animal — не Dog!
console.log(new Dog('Rex').constructor === Dog) // false
// Исправлено:
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog // восстановить!Ошибка 2: изменение встроенных прототипов
// НИКОГДА так не делай — ломает совместимость:
Array.prototype.last = function() { return this[this.length - 1] }
String.prototype.reverse = function() { return [...this].reverse().join('') }
// Исправлено — используй вспомогательные функции:
function last(arr) { return arr[arr.length - 1] }Ошибка 3: установка прототипа через __proto__
// Устарело и медленно:
dog.__proto__ = animal
// Современно:
Object.setPrototypeOf(dog, animal)
// Или при создании:
const dog = Object.create(animal)[[Prototype]], ошибки типа «x is not a function» часто связаны с цепочкойИерархия пользователей через прототипное наследование
// Базовый конструктор
function User(name, email) {
this.name = name
this.email = email
this.createdAt = new Date().toISOString()
}
User.prototype.toString = function() {
return `User(${this.name}, ${this.email})`
}
User.prototype.canAccess = function(resource) {
return false // базовая реализация — нет доступа
}
// AdminUser — наследует от User
function AdminUser(name, email, department) {
User.call(this, name, email) // вызов родительского конструктора
this.department = department
this.permissions = new Set(['read', 'write', 'delete'])
}
AdminUser.prototype = Object.create(User.prototype)
AdminUser.prototype.constructor = AdminUser
AdminUser.prototype.canAccess = function(resource) {
return true // администратор — полный доступ
}
AdminUser.prototype.grant = function(permission) {
this.permissions.add(permission)
return this
}
AdminUser.prototype.toString = function() {
return `Admin(${this.name}, dept: ${this.department})`
}
// Создание экземпляров
const user = new User('Алексей', 'alex@mail.ru')
const admin = new AdminUser('Мария', 'maria@mail.ru', 'IT')
console.log(user.toString()) // 'User(Алексей, alex@mail.ru)'
console.log(admin.toString()) // 'Admin(Мария, dept: IT)'
console.log(user.canAccess('files')) // false
console.log(admin.canAccess('files')) // true
// Проверка цепочки прототипов
console.log(admin instanceof AdminUser) // true
console.log(admin instanceof User) // true — цепочка работает!
console.log(admin.constructor === AdminUser) // true
// hasOwnProperty
console.log(admin.hasOwnProperty('name')) // true — собственное
console.log(admin.hasOwnProperty('canAccess')) // false — из прототипа
// Один метод для всех User — экономия памяти
const user2 = new User('Иван', 'ivan@mail.ru')
console.log(user.toString === user2.toString) // true — одна функция!
// Object.keys — только собственные свойства
console.log(Object.keys(admin))
// ['name', 'email', 'createdAt', 'department', 'permissions']В интернет-магазине тысячи объектов-товаров. У каждого есть методы getPrice(), formatName(), isAvailable(). Если создавать эти методы внутри конструктора — каждый объект получит свою копию функции. Тысяча товаров = тысяча одинаковых функций в памяти. Прототипы решают это: метод существует в одном экземпляре, а все объекты его разделяют.
Каждый объект в JavaScript имеет скрытую ссылку [[Prototype]] на другой объект — свой прототип. При обращении к свойству объекта JS ищет его сначала в самом объекте, потом в прототипе, потом в прототипе прототипа — и так до null. Это цепочка прототипов.
const animal = { eats: true, breathes: true }
const dog = { barks: true }
// Устанавливаем прототип: dog[[Prototype]] = animal
Object.setPrototypeOf(dog, animal)
console.log(dog.barks) // true — собственное свойство
console.log(dog.eats) // true — найдено в прототипе animal
console.log(dog.flies) // undefined — нет нигде в цепочке
// Цепочка: dog → animal → Object.prototype → nullconst vehicleProto = {
describe() {
return `${this.brand} ${this.model} (${this.year})`
},
isNew() {
return new Date().getFullYear() - this.year < 3
}
}
const car = Object.create(vehicleProto)
car.brand = 'Toyota'
car.model = 'Camry'
car.year = 2022
console.log(car.describe()) // 'Toyota Camry (2022)'
console.log(Object.getPrototypeOf(car) === vehicleProto) // trueМетоды в Constructor.prototype разделяются всеми экземплярами — один объект функции в памяти:
function Product(name, price) {
// собственные свойства — уникальны для каждого экземпляра
this.name = name
this.price = price
}
// Методы — в прототипе — один раз для всех
Product.prototype.getPrice = function() {
return `${this.price} ₽`
}
Product.prototype.isExpensive = function() {
return this.price > 10000
}
const laptop = new Product('Ноутбук', 75000)
const mouse = new Product('Мышь', 1500)
console.log(laptop.getPrice()) // '75000 ₽'
console.log(mouse.isExpensive()) // false
// Один объект метода разделяется всеми экземплярами:
console.log(laptop.getPrice === mouse.getPrice) // true — одна функция!function Animal(name) {
this.name = name
}
Animal.prototype.speak = function() {
return `${this.name} издаёт звук`
}
function Dog(name, breed) {
Animal.call(this, name) // вызов родительского конструктора
this.breed = breed
}
// Dog.prototype наследует от Animal.prototype
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog // восстанавливаем constructor
// Переопределяем / добавляем методы
Dog.prototype.speak = function() {
return `${this.name} лает: Гав!`
}
Dog.prototype.fetch = function() {
return `${this.name} приносит мяч`
}
const rex = new Dog('Рекс', 'Лабрадор')
console.log(rex.speak()) // 'Рекс лает: Гав!'
console.log(rex.fetch()) // 'Рекс приносит мяч'
console.log(rex instanceof Dog) // true
console.log(rex instanceof Animal) // true — цепочка работаетconsole.log(rex.hasOwnProperty('name')) // true — собственное
console.log(rex.hasOwnProperty('speak')) // false — из прототипа
// В цикле for...in — свои + прототипные. Фильтруй если нужно:
for (const key in rex) {
if (rex.hasOwnProperty(key)) {
console.log(key, rex[key]) // только name и breed
}
}Ошибка 1: забыть восстановить constructor
// Сломано:
Dog.prototype = Object.create(Animal.prototype)
// Dog.prototype.constructor теперь Animal — не Dog!
console.log(new Dog('Rex').constructor === Dog) // false
// Исправлено:
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog // восстановить!Ошибка 2: изменение встроенных прототипов
// НИКОГДА так не делай — ломает совместимость:
Array.prototype.last = function() { return this[this.length - 1] }
String.prototype.reverse = function() { return [...this].reverse().join('') }
// Исправлено — используй вспомогательные функции:
function last(arr) { return arr[arr.length - 1] }Ошибка 3: установка прототипа через __proto__
// Устарело и медленно:
dog.__proto__ = animal
// Современно:
Object.setPrototypeOf(dog, animal)
// Или при создании:
const dog = Object.create(animal)[[Prototype]], ошибки типа «x is not a function» часто связаны с цепочкойИерархия пользователей через прототипное наследование
// Базовый конструктор
function User(name, email) {
this.name = name
this.email = email
this.createdAt = new Date().toISOString()
}
User.prototype.toString = function() {
return `User(${this.name}, ${this.email})`
}
User.prototype.canAccess = function(resource) {
return false // базовая реализация — нет доступа
}
// AdminUser — наследует от User
function AdminUser(name, email, department) {
User.call(this, name, email) // вызов родительского конструктора
this.department = department
this.permissions = new Set(['read', 'write', 'delete'])
}
AdminUser.prototype = Object.create(User.prototype)
AdminUser.prototype.constructor = AdminUser
AdminUser.prototype.canAccess = function(resource) {
return true // администратор — полный доступ
}
AdminUser.prototype.grant = function(permission) {
this.permissions.add(permission)
return this
}
AdminUser.prototype.toString = function() {
return `Admin(${this.name}, dept: ${this.department})`
}
// Создание экземпляров
const user = new User('Алексей', 'alex@mail.ru')
const admin = new AdminUser('Мария', 'maria@mail.ru', 'IT')
console.log(user.toString()) // 'User(Алексей, alex@mail.ru)'
console.log(admin.toString()) // 'Admin(Мария, dept: IT)'
console.log(user.canAccess('files')) // false
console.log(admin.canAccess('files')) // true
// Проверка цепочки прототипов
console.log(admin instanceof AdminUser) // true
console.log(admin instanceof User) // true — цепочка работает!
console.log(admin.constructor === AdminUser) // true
// hasOwnProperty
console.log(admin.hasOwnProperty('name')) // true — собственное
console.log(admin.hasOwnProperty('canAccess')) // false — из прототипа
// Один метод для всех User — экономия памяти
const user2 = new User('Иван', 'ivan@mail.ru')
console.log(user.toString === user2.toString) // true — одна функция!
// Object.keys — только собственные свойства
console.log(Object.keys(admin))
// ['name', 'email', 'createdAt', 'department', 'permissions']Создай систему контента для блога. Конструктор Content(title, author) хранит title, author, createdAt (текущая дата ISO), views=0. В прототипе: view() — увеличивает views и возвращает this, getAge() — возвращает количество дней с момента создания (используй Date.now()). Конструктор Article(title, author, category) наследует от Content через Object.create. Добавь в прототип Article: publish() — устанавливает isPublished=true и возвращает this, getInfo() — возвращает строку формата "[category] title by author (N просмотров)".
Content.call(this, title, author) в конструкторе Article. Article.prototype = Object.create(Content.prototype). getInfo: `[${this.category}] ${this.title} by ${this.author} (${this.views} просмотров)`.