Конструкторы и 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 выполняет примерно следующее:
- Создаёт пустой объект и связывает его с
prototypeфункции-конструктора. - Вызывает функцию с
this, указывающим на этот объект. - Если функция не вернула собственный объект, возвращает созданный экземпляр.
Без 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.