Конструкторы и new

Конструкторы и new #

Конструктор — функция, вызываемая с new, которая создаёт и инициализирует новый объект. Внутри такой функции this ссылается на свежий экземпляр. Паттерн предшествует синтаксису class и по-прежнему встречается в legacy-коде и встроенных типах (Date, Map, Error).


Вызов с new #

function Cart(id, currency = 'RUB') {
  this.id = id
  this.currency = currency
  this.items = []
  this.updatedAt = null
}

const cart = new Cart('cart-7a2')
cart.id // "cart-7a2"
cart.items // []

Оператор new выполняет примерно следующее:

  1. Создаёт пустой объект и связывает его с prototype функции-конструктора.
  2. Вызывает функцию с this, указывающим на этот объект.
  3. Если функция не вернула собственный объект, возвращает созданный экземпляр.

Без new обычный вызов конструктора в strict mode бросит ошибку или испортит глобальные привязки — всегда используйте new с функциями-конструкторами.


Методы на прототипе #

Чтобы не дублировать функции на каждом экземпляре, методы добавляют в Constructor.prototype:

function Cart(id) {
  this.id = id
  this.items = []
}

Cart.prototype.addItem = function (sku, qty) {
  this.items.push({ sku, qty })
}

Cart.prototype.totalQty = function () {
  return this.items.reduce((n, line) => n + line.qty, 0)
}

const a = new Cart('cart-a')
const b = new Cart('cart-b')

a.addItem === b.addItem // true — один метод на прототипе

Устройство цепочки прототипов и классов ES6 — в разделе ООП. Здесь важно: new + prototype — классический способ «наследования» до появления class.


Проверка типа экземпляра #

instanceof сравнивает прототип цепочки объекта с prototype конструктора:

const cart = new Cart('cart-7a2')

cart instanceof Cart // true
cart instanceof Object // true

Для встроенных типов и кастомных классов тот же оператор; для плоских литералов instanceof бесполезен — там нет конструктора.


Литерал против конструктора #

ПодходКогда уместен
литерал { }одиночный конфиг, DTO, JSON-подобные данные
new Constructor()много однотипных экземпляров с общими методами
class (ES6)современная запись того же сахара над прототипом
// литерал — статический payload
const apiPayload = {
  event: 'profile.updated',
  data: { userId: 'u-104', displayName: 'Мария К.' },
}

// конструктор — фабрика однотипных сущностей
function UserProfile(userId, displayName) {
  this.userId = userId
  this.displayName = displayName
  this.updatedAt = new Date()
}

const profile = new UserProfile('u-104', 'Мария К.')

Для простых структур без методов литерал короче и привычнее — см. основы объектов.


Возврат из конструктора #

Если конструктор явно return объект (не примитив), new отдаёт его вместо созданного по умолчанию:

function BrokenCart() {
  this.id = 'temp'
  return { id: 'external' }
}

const c = new BrokenCart()
c.id // "external"

Возврат примитива или return без значения игнорируется — остаётся новый this. В прикладных конструкторах не возвращайте посторонние объекты без веской причины.


Защита от вызова без new #

В библиотеках иногда проверяют new.target:

function Cart(id) {
  if (!new.target) {
    throw new TypeError("Cart требует вызов с 'new'")
  }
  this.id = id
}

Или используют фабричную функцию без new:

function createCart(id) {
  return {
    id,
    items: [],
    addItem(sku, qty) {
      this.items.push({ sku, qty })
    },
  }
}

Фабрика возвращает литерал — this в методах снова работает через точку вызова; см. Методы и this.


Встроенные конструкторы #

const now = new Date()
const map = new Map([['sku', 'kb-01']])
const err = new Error('Cart not found')

Для объектов-примитивов-обёрток (new String, new Number) в современном коде предпочитают литералы и примитивы из переменных и типов.


Итог #

  • new Constructor() создаёт объект, связывает прототип и вызывает функцию с нужным this.
  • Общие методы экономнее вешать на Constructor.prototype, а не на каждый экземпляр.
  • instanceof проверяет принадлежность к цепочке прототипов конструктора.
  • Литералы — для данных; конструкторы и классы — для множества экземпляров с поведением.
  • Явный return объекта из конструктора подменяет результат new.

См. также #

Часто спрашивают

Почему this «потерялся» в колбэке?
У обычной функции this зависит от способа вызова. Сохраните контекст через bind или используйте стрелочную функцию осознанно. Подробнее