Всё сводится к пропуску одного слова. Ты пишешь const shirt = Product('Футболка', 1500) вместо const shirt = new Product(...). Отсутствие new превращает вызов конструктора в обычный вызов функции — и все правила из главы 2 начинают действовать: this становится глобальным объектом или undefined в строгом режиме. Функция, задуманная для создания объекта, теперь заполняет глобальный скоуп или падает с ошибкой.
Что происходит без new
function Product(name, price) {
this.name = name;
this.price = price;
}
// правильно
const shirt = new Product('Футболка', 1500);
console.log(shirt.name); // => 'Футболка'
// забыли new — в нестрогом режиме
const oops = Product('Штаны', 2500);
console.log(oops); // => undefined (функция ничего не вернула)
console.log(window.name); // => 'Штаны' (!)
console.log(window.price); // => 2500 (!)
Функция без new отработала как простой вызов. this внутри неё — window. this.name = name тихо создало window.name, а сама функция ничего не вернула. В коде «const oops = Product(...)» получаем undefined. Баг сложно найти: логика работает не так, но нет ошибки, чтобы отследить где именно.
В строгом режиме сценарий громче:
'use strict';
function Product(name, price) {
this.name = name; // TypeError: Cannot set properties of undefined
this.price = price;
}
Product('Штаны', 2500); // TypeError на первой же строке функции
Уже не тихо: ошибка сразу, стек-трейс указывает на строку. Отладить намного проще.
Защита 1: используй class-синтаксис
Классы ES6 (2015 год) добавили в язык защиту от этой ошибки. Вызов класса без new бросает TypeError автоматически, без всякой проверки:
class Product {
constructor(name, price) {
this.name = name;
this.price = price;
}
}
Product('Штаны', 2500);
// TypeError: Class constructor Product cannot be invoked without 'new'
Это самое надёжное решение: движок JavaScript сам следит, чтобы класс всегда вызывался через new. Никакой boilerplate-проверки писать не надо.
Защита 2: new.target
Если по какой-то причине нужно оставить function-style конструктор (не class), внутри можно проверить, был ли вызов через new, через мета-свойство new.target. Оно вернёт саму функцию, если её позвали через new, и undefined в противном случае:
function Product(name, price) {
if (!new.target) {
// забыли new — самостоятельно вызовем себя через new
return new Product(name, price);
}
this.name = name;
this.price = price;
}
// оба вызова работают одинаково
const a = new Product('X', 100);
const b = Product('Y', 200); // без new, но всё равно новый объект
console.log(a instanceof Product); // => true
console.log(b instanceof Product); // => true
Такой паттерн делает функцию «forgiving»: работает и с new, и без него. Использовалось в старых библиотеках вроде jQuery-плагинов, где авторы не хотели заставлять пользователей помнить про new. В современном коде лучше просто использовать class-синтаксис — он даёт более чёткий сигнал об ошибке.
Защита 3: соглашение об именах
Самое базовое — называть конструкторы с заглавной буквы (PascalCase), а обычные функции с маленькой (camelCase):
// конструкторы — PascalCase
class User { /* ... */ }
class Order { /* ... */ }
function Legacy() { /* ... */ }
// обычные функции — camelCase
function computeTotal() { /* ... */ }
function formatDate() { /* ... */ }
Соглашение не гарантия — забыть new всё равно можно — но помогает читать код: если видишь const x = User(...) вместо const x = new User(...), PascalCase-имя должно триггерить внутренний код-ревью. Плюс современные линтеры (ESLint правило new-cap) ловят эту ошибку статически.
Реальность 2026
В современном коде проблема «забыли new» встречается заметно реже, чем в 2010-х. Причины:
- Class-синтаксис заменил function-style-конструкторы почти везде. Class всегда бросает TypeError без new.
- Функциональные React-компоненты и hooks убрали большую часть OOP-кода в UI-слое.
- TypeScript отлавливает вызовы класса без new на этапе компиляции: ошибка Value of type 'typeof MyClass' is not callable.
- ESLint правило no-new и new-cap в стандартных конфигах.
Но легаси-код с function-конструкторами всё ещё встречается — в jQuery-плагинах, старых Node-модулях, embedded-скриптах. И там знание про «new — обязателен» остаётся ключевым при отладке.