← JavaScript/F.prototype и Object.create#116 из 383← ПредыдущийСледующий →+25 XP
Полезно по теме:Гайд: как учить JavaScriptПрактика: JS базаПрактика: async и сетьТермин: Closure

F.prototype и Object.create

Все библиотеки до 2015 года — jQuery, Backbone, Underscore — строили иерархии объектов без ключевого слова class. Чтобы понять их код и знать, как class работает под капотом, нужно разобраться в F.prototype и Object.create. Это тот же механизм — просто без синтаксического сахара.

На основе предыдущих уроков

  • «Конструктор/new» — что делает new: создаёт объект, устанавливает this, возвращает его
  • «Прототипы» — цепочка [[Prototype]], поиск свойств вверх по цепочке
  • «Классы» — class — это синтаксический сахар над тем, что изучается здесь
  • «instanceof» — работает через цепочку прототипов
  • Свойство prototype у функций-конструкторов

    Каждая функция автоматически получает свойство prototype — обычный объект с единственным полем constructor, ссылающимся обратно на саму функцию:

    function User(name) {
      this.name = name
    }
    
    console.log(User.prototype)                          // { constructor: [Function: User] }
    console.log(User.prototype.constructor === User)     // true

    Как new Func() устанавливает __proto__

    Когда вы вызываете функцию через 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 property

    Свойство 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) — создание объекта с заданным прототипом

    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

    Разница между Object.create и new

    | | new Func() | Object.create(proto) |

    |---|---|---|

    | Вызывает конструктор | Да | Нет |

    | Устанавливает __proto__ | Func.prototype | Переданный proto |

    | Возвращает | Новый объект | Новый объект |

    Иерархия Animal → Dog без class

    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)  // true

    Типичные ошибки

    1. 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 = Dog

    3. Вызов Animal.call(this) после Dog.prototype = ... — порядок не важен, но:

    // Animal.call(this, ...) должен быть в теле функции Dog,
    // а Dog.prototype = Object.create(...) — снаружи, после объявления.
    // Эти операции независимы: первая настраивает экземпляр, вторая — цепочку.

    В реальных проектах

  • Legacy-код — jQuery, Backbone, AngularJS 1.x используют этот паттерн
  • Понимание `class` под капотом — transpilers (Babel) компилируют class в F.prototype
  • Object.create(null) — словари без унаследованных методов: нет toString, hasOwnProperty
  • Фабричные функции — Object.create(proto) + заполнение свойств без new
  • Примеры

    Иерархия 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

    F.prototype и Object.create

    Все библиотеки до 2015 года — jQuery, Backbone, Underscore — строили иерархии объектов без ключевого слова class. Чтобы понять их код и знать, как class работает под капотом, нужно разобраться в F.prototype и Object.create. Это тот же механизм — просто без синтаксического сахара.

    На основе предыдущих уроков

  • «Конструктор/new» — что делает new: создаёт объект, устанавливает this, возвращает его
  • «Прототипы» — цепочка [[Prototype]], поиск свойств вверх по цепочке
  • «Классы» — class — это синтаксический сахар над тем, что изучается здесь
  • «instanceof» — работает через цепочку прототипов
  • Свойство prototype у функций-конструкторов

    Каждая функция автоматически получает свойство prototype — обычный объект с единственным полем constructor, ссылающимся обратно на саму функцию:

    function User(name) {
      this.name = name
    }
    
    console.log(User.prototype)                          // { constructor: [Function: User] }
    console.log(User.prototype.constructor === User)     // true

    Как new Func() устанавливает __proto__

    Когда вы вызываете функцию через 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 property

    Свойство 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) — создание объекта с заданным прототипом

    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

    Разница между Object.create и new

    | | new Func() | Object.create(proto) |

    |---|---|---|

    | Вызывает конструктор | Да | Нет |

    | Устанавливает __proto__ | Func.prototype | Переданный proto |

    | Возвращает | Новый объект | Новый объект |

    Иерархия Animal → Dog без class

    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)  // true

    Типичные ошибки

    1. 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 = Dog

    3. Вызов Animal.call(this) после Dog.prototype = ... — порядок не важен, но:

    // Animal.call(this, ...) должен быть в теле функции Dog,
    // а Dog.prototype = Object.create(...) — снаружи, после объявления.
    // Эти операции независимы: первая настраивает экземпляр, вторая — цепочку.

    В реальных проектах

  • Legacy-код — jQuery, Backbone, AngularJS 1.x используют этот паттерн
  • Понимание `class` под капотом — transpilers (Babel) компилируют class в F.prototype
  • Object.create(null) — словари без унаследованных методов: нет toString, hasOwnProperty
  • Фабричные функции — Object.create(proto) + заполнение свойств без new
  • Примеры

    Иерархия 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) на созданном объекте.

    Загружаем среду выполнения...
    Загружаем AI-помощника...