Все библиотеки до 2015 года — jQuery, Backbone, Underscore — строили иерархии объектов без ключевого слова class. Чтобы понять их код и знать, как class работает под капотом, нужно разобраться в F.prototype и Object.create. Это тот же механизм — просто без синтаксического сахара.
new: создаёт объект, устанавливает this, возвращает его[[Prototype]], поиск свойств вверх по цепочкеclass — это синтаксический сахар над тем, что изучается здесьКаждая функция автоматически получает свойство prototype — обычный объект с единственным полем constructor, ссылающимся обратно на саму функцию:
function User(name) {
this.name = name
}
console.log(User.prototype) // { constructor: [Function: User] }
console.log(User.prototype.constructor === User) // trueКогда вы вызываете функцию через new, JavaScript делает три вещи:
1. Создаёт пустой объект {}
2. Устанавливает его __proto__ равным Func.prototype
3. Выполняет тело функции, передав новый объект как this
function Product(name, price) {
this.name = name
this.price = price
}
Product.prototype.getInfo = function() {
return `${this.name} — ${this.price} ₽`
}
const milk = new Product('Молоко', 89)
const bread = new Product('Хлеб', 45)
console.log(milk.getInfo()) // 'Молоко — 89 ₽'
console.log(bread.getInfo()) // 'Хлеб — 45 ₽'
// Оба объекта используют ОДИН метод из прототипа:
console.log(milk.getInfo === bread.getInfo) // trueСвойство constructor позволяет создавать новый экземпляр того же типа, не зная имени конструктора:
function Order(id) {
this.id = id
}
const order1 = new Order(1)
const order2 = new order1.constructor(2) // то же самое, что new Order(2)
console.log(order2 instanceof Order) // trueВажно: если полностью перезаписать prototype, свойство constructor теряется:
function Car(model) { this.model = model }
// Плохо — constructor потерян:
Car.prototype = { drive() { return 'Еду!' } }
// Хорошо — сохраняем constructor:
Car.prototype = {
constructor: Car,
drive() { return 'Еду!' }
}Object.create(proto) создаёт новый пустой объект, у которого [[Prototype]] равен proto:
const vehicleProto = {
startEngine() {
return `${this.model}: двигатель запущен`
},
stop() {
return `${this.model}: остановлен`
}
}
const car = Object.create(vehicleProto)
car.model = 'Toyota Camry'
console.log(car.startEngine()) // 'Toyota Camry: двигатель запущен'
console.log(Object.getPrototypeOf(car) === vehicleProto) // true| | new Func() | Object.create(proto) |
|---|---|---|
| Вызывает конструктор | Да | Нет |
| Устанавливает __proto__ | Func.prototype | Переданный proto |
| Возвращает | Новый объект | Новый объект |
function Animal(name, sound) {
this.name = name
this.sound = sound
}
Animal.prototype.speak = function() {
return `${this.name} говорит: ${this.sound}!`
}
function Dog(name) {
Animal.call(this, name, 'Гав') // аналог super()
this.tricks = []
}
// Настройка наследования: Dog.prototype → Animal.prototype
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog // восстанавливаем constructor
Dog.prototype.learnTrick = function(trick) {
this.tricks.push(trick)
}
const rex = new Dog('Рекс')
rex.learnTrick('сидеть')
console.log(rex.speak()) // 'Рекс говорит: Гав!'
console.log(rex instanceof Dog) // true
console.log(rex instanceof Animal) // true1. Dog.prototype = Animal.prototype — вместо Object.create — мутирует родительский прототип:
// Плохо: Dog.prototype и Animal.prototype — один и тот же объект
Dog.prototype = Animal.prototype
Dog.prototype.learnTrick = function() { ... }
// Теперь learnTrick есть и у Animal! Прототип испорчен.
// Хорошо: создаём новый объект с Animal.prototype в цепочке
Dog.prototype = Object.create(Animal.prototype)2. Забыть восстановить constructor после перезаписи prototype:
Dog.prototype = Object.create(Animal.prototype)
// Теперь Dog.prototype.constructor === Animal — неправильно!
const d = new Dog('Бобик')
console.log(d.constructor === Dog) // false — конструктор потерян
console.log(d.constructor === Animal) // true — неожиданно!
// Правильно: всегда восстанавливай
Dog.prototype.constructor = Dog3. Вызов Animal.call(this) после Dog.prototype = ... — порядок не важен, но:
// Animal.call(this, ...) должен быть в теле функции Dog,
// а Dog.prototype = Object.create(...) — снаружи, после объявления.
// Эти операции независимы: первая настраивает экземпляр, вторая — цепочку.toString, hasOwnPropertyИерархия Animal → Dog через функции-конструкторы и prototype без синтаксиса class
// Базовый "класс" Animal
function Animal(name, sound) {
this.name = name
this.sound = sound
}
Animal.prototype.speak = function() {
return `${this.name} говорит: ${this.sound}!`
}
Animal.prototype.describe = function() {
return `Животное: ${this.name}`
}
// Дочерний "класс" Dog
function Dog(name, breed) {
Animal.call(this, name, 'Гав') // super(name, 'Гав')
this.breed = breed
this.tricks = []
}
// Наследование: Dog.prototype → Animal.prototype → Object.prototype
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog // восстанавливаем constructor
// Методы Dog
Dog.prototype.learnTrick = function(trick) {
this.tricks.push(trick)
return this
}
Dog.prototype.showTricks = function() {
if (this.tricks.length === 0) return `${this.name} ещё ничему не обучен`
return `${this.name} (${this.breed}) умеет: ${this.tricks.join(', ')}`
}
// Переопределение метода (override)
Dog.prototype.describe = function() {
return `Собака ${this.breed}: ${this.name}`
}
const cat = new Animal('Мурка', 'Мяу')
const dog = new Dog('Барсик', 'Лабрадор')
console.log(cat.speak()) // 'Мурка говорит: Мяу!'
console.log(cat.describe()) // 'Животное: Мурка'
console.log(dog.speak()) // 'Барсик говорит: Гав!'
console.log(dog.describe()) // 'Собака Лабрадор: Барсик'
dog.learnTrick('сидеть').learnTrick('давать лапу').learnTrick('рядом')
console.log(dog.showTricks())
// 'Барсик (Лабрадор) умеет: сидеть, давать лапу, рядом'
// Проверяем цепочку прототипов
console.log(dog instanceof Dog) // true
console.log(dog instanceof Animal) // true
console.log(dog.constructor === Dog) // true
// Прообраз цепочки:
// dog → Dog.prototype → Animal.prototype → Object.prototype → null
console.log(Object.getPrototypeOf(dog) === Dog.prototype) // true
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype) // trueВсе библиотеки до 2015 года — jQuery, Backbone, Underscore — строили иерархии объектов без ключевого слова class. Чтобы понять их код и знать, как class работает под капотом, нужно разобраться в F.prototype и Object.create. Это тот же механизм — просто без синтаксического сахара.
new: создаёт объект, устанавливает this, возвращает его[[Prototype]], поиск свойств вверх по цепочкеclass — это синтаксический сахар над тем, что изучается здесьКаждая функция автоматически получает свойство prototype — обычный объект с единственным полем constructor, ссылающимся обратно на саму функцию:
function User(name) {
this.name = name
}
console.log(User.prototype) // { constructor: [Function: User] }
console.log(User.prototype.constructor === User) // trueКогда вы вызываете функцию через new, JavaScript делает три вещи:
1. Создаёт пустой объект {}
2. Устанавливает его __proto__ равным Func.prototype
3. Выполняет тело функции, передав новый объект как this
function Product(name, price) {
this.name = name
this.price = price
}
Product.prototype.getInfo = function() {
return `${this.name} — ${this.price} ₽`
}
const milk = new Product('Молоко', 89)
const bread = new Product('Хлеб', 45)
console.log(milk.getInfo()) // 'Молоко — 89 ₽'
console.log(bread.getInfo()) // 'Хлеб — 45 ₽'
// Оба объекта используют ОДИН метод из прототипа:
console.log(milk.getInfo === bread.getInfo) // trueСвойство constructor позволяет создавать новый экземпляр того же типа, не зная имени конструктора:
function Order(id) {
this.id = id
}
const order1 = new Order(1)
const order2 = new order1.constructor(2) // то же самое, что new Order(2)
console.log(order2 instanceof Order) // trueВажно: если полностью перезаписать prototype, свойство constructor теряется:
function Car(model) { this.model = model }
// Плохо — constructor потерян:
Car.prototype = { drive() { return 'Еду!' } }
// Хорошо — сохраняем constructor:
Car.prototype = {
constructor: Car,
drive() { return 'Еду!' }
}Object.create(proto) создаёт новый пустой объект, у которого [[Prototype]] равен proto:
const vehicleProto = {
startEngine() {
return `${this.model}: двигатель запущен`
},
stop() {
return `${this.model}: остановлен`
}
}
const car = Object.create(vehicleProto)
car.model = 'Toyota Camry'
console.log(car.startEngine()) // 'Toyota Camry: двигатель запущен'
console.log(Object.getPrototypeOf(car) === vehicleProto) // true| | new Func() | Object.create(proto) |
|---|---|---|
| Вызывает конструктор | Да | Нет |
| Устанавливает __proto__ | Func.prototype | Переданный proto |
| Возвращает | Новый объект | Новый объект |
function Animal(name, sound) {
this.name = name
this.sound = sound
}
Animal.prototype.speak = function() {
return `${this.name} говорит: ${this.sound}!`
}
function Dog(name) {
Animal.call(this, name, 'Гав') // аналог super()
this.tricks = []
}
// Настройка наследования: Dog.prototype → Animal.prototype
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog // восстанавливаем constructor
Dog.prototype.learnTrick = function(trick) {
this.tricks.push(trick)
}
const rex = new Dog('Рекс')
rex.learnTrick('сидеть')
console.log(rex.speak()) // 'Рекс говорит: Гав!'
console.log(rex instanceof Dog) // true
console.log(rex instanceof Animal) // true1. Dog.prototype = Animal.prototype — вместо Object.create — мутирует родительский прототип:
// Плохо: Dog.prototype и Animal.prototype — один и тот же объект
Dog.prototype = Animal.prototype
Dog.prototype.learnTrick = function() { ... }
// Теперь learnTrick есть и у Animal! Прототип испорчен.
// Хорошо: создаём новый объект с Animal.prototype в цепочке
Dog.prototype = Object.create(Animal.prototype)2. Забыть восстановить constructor после перезаписи prototype:
Dog.prototype = Object.create(Animal.prototype)
// Теперь Dog.prototype.constructor === Animal — неправильно!
const d = new Dog('Бобик')
console.log(d.constructor === Dog) // false — конструктор потерян
console.log(d.constructor === Animal) // true — неожиданно!
// Правильно: всегда восстанавливай
Dog.prototype.constructor = Dog3. Вызов Animal.call(this) после Dog.prototype = ... — порядок не важен, но:
// Animal.call(this, ...) должен быть в теле функции Dog,
// а Dog.prototype = Object.create(...) — снаружи, после объявления.
// Эти операции независимы: первая настраивает экземпляр, вторая — цепочку.toString, hasOwnPropertyИерархия Animal → Dog через функции-конструкторы и prototype без синтаксиса class
// Базовый "класс" Animal
function Animal(name, sound) {
this.name = name
this.sound = sound
}
Animal.prototype.speak = function() {
return `${this.name} говорит: ${this.sound}!`
}
Animal.prototype.describe = function() {
return `Животное: ${this.name}`
}
// Дочерний "класс" Dog
function Dog(name, breed) {
Animal.call(this, name, 'Гав') // super(name, 'Гав')
this.breed = breed
this.tricks = []
}
// Наследование: Dog.prototype → Animal.prototype → Object.prototype
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog // восстанавливаем constructor
// Методы Dog
Dog.prototype.learnTrick = function(trick) {
this.tricks.push(trick)
return this
}
Dog.prototype.showTricks = function() {
if (this.tricks.length === 0) return `${this.name} ещё ничему не обучен`
return `${this.name} (${this.breed}) умеет: ${this.tricks.join(', ')}`
}
// Переопределение метода (override)
Dog.prototype.describe = function() {
return `Собака ${this.breed}: ${this.name}`
}
const cat = new Animal('Мурка', 'Мяу')
const dog = new Dog('Барсик', 'Лабрадор')
console.log(cat.speak()) // 'Мурка говорит: Мяу!'
console.log(cat.describe()) // 'Животное: Мурка'
console.log(dog.speak()) // 'Барсик говорит: Гав!'
console.log(dog.describe()) // 'Собака Лабрадор: Барсик'
dog.learnTrick('сидеть').learnTrick('давать лапу').learnTrick('рядом')
console.log(dog.showTricks())
// 'Барсик (Лабрадор) умеет: сидеть, давать лапу, рядом'
// Проверяем цепочку прототипов
console.log(dog instanceof Dog) // true
console.log(dog instanceof Animal) // true
console.log(dog.constructor === Dog) // true
// Прообраз цепочки:
// dog → Dog.prototype → Animal.prototype → Object.prototype → null
console.log(Object.getPrototypeOf(dog) === Dog.prototype) // true
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype) // trueВ системе расчёта геометрии используй Object.create для создания объектов Circle и Rectangle с общим прототипом Shape. Реализуй фабрику createShape(type, ...args), где оба типа имеют метод area() и toString().
Object.create(Shape) создаёт CircleProto и RectangleProto с Shape в цепочке прототипов. Внутри createShape используй Object.create(CircleProto) для круга и Object.create(RectangleProto) для прямоугольника, затем задай свойства (radius или width/height) на созданном объекте.