Конструкторы и оператор new: полное руководство
Основы работы с оператором new
Оператор new используется для создания экземпляра пользовательского типа объекта или встроенного объекта с конструктором. При вызове функции через new выполняется следующий алгоритм:
- Создаётся новый пустой объект.
- Прототипом этого объекта становится свойство prototype функции-конструктора.
- Внутреннее значение this привязывается к новому объекту.
- Выполняется тело конструктора: свойства и методы добавляются к this.
- Если конструктор не возвращает явно объект, возвращается созданный объект (возврат примитива игнорируется).
Пример базового использования:
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function() {
return `Привет, я ${this.name}`;
};
}
const user = new Person('Мария', 30);
console.log(user.name); // Мария
console.log(user.greet()); // Привет, я Мария
Js new (оператор new в javascript)
При отсутствии new конструктор выполняется как обычная функция, и this ссылается на глобальный объект (window в браузере, global в Node.js), что приводит к загрязнению глобальной области и некорректным свойствам.
Типичная ошибка: забыть оператор new. Например, const user = Person('Мария', 30); - this внутри функции ссылается на глобальный объект, свойства name и age становятся глобальными, а сама переменная user получает значение undefined. Для защиты используется проверка с instanceof или свойство new.target.
Как защититься от вызова конструктора без new?
Можно внутри конструктора проверить, был ли он вызван через new, и при необходимости автоматически создать экземпляр.
function Car(model) {
if (!(this instanceof Car)) {
return new Car(model);
}
this.model = model;
}
const car1 = new Car('Tesla');
const car2 = Car('BMW'); // работает как new
console.log(car1.model); // Tesla
console.log(car2.model); // BMW
Проблема: такой подход работает для конструкторов, не возвращающих объект. Если конструктор возвращает объект явно, проверка instanceof может дать ложный результат. Альтернативный способ - использовать new.target (доступен в ES2015):
function Car(model) {
if (!new.target) {
throw new Error('Car must be called with new');
// или return new Car(model);
}
this.model = model;
}
Как создать объект без оператора new, используя Object.create?
Метод Object.create позволяет создать объект с заданным прототипом, имитируя работу new без вызова конструктора.
const personProto = {
greet() {
return `Привет, я ${this.name}`;
}
};
function createPerson(name, age) {
let obj = Object.create(personProto);
obj.name = name;
obj.age = age;
return obj;
}
const user = createPerson('Иван', 25);
console.log(user.greet()); // Привет, я Иван
Этот способ часто используется для наследования через прототипы без конструкторов.
Как работает возврат значения из конструктора?
Если конструктор возвращает примитив (число, строку, булево, null, undefined), то оператор new игнорирует этот возврат и возвращает созданный объект. Если же возвращается объект (включая функции, массивы), то возвращается именно этот объект, а не this.
function Game(title) {
this.title = title;
return { custom: true }; // возвращаем другой объект
}
const g = new Game('Doom');
console.log(g.title); // undefined - свойство не установлено
console.log(g.custom); // true
Ошибка: случайный возврат объекта из конструктора приводит к потере всех свойств, добавленных через this. Конструктор не должен возвращать объекты (только примитивы или ничего).
Как использовать class для замены конструктора с new?
Синтаксис class (ES2015) является синтаксическим сахаром для функций-конструкторов и прототипного наследования. Метод constructor вызывается при new.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} издаёт звук`;
}
}
const dog = new Animal('Собака');
console.log(dog.speak()); // Собака издаёт звук
Классы строго требуют вызова через new; вызов без new вызывает ошибку TypeError.
Расширенные примеры использования оператора new
Пример 1. Конструктор с прототипными методами:
function Book(title, author) {
this.title = title;
this.author = author;
}
Book.prototype.getInfo = function() {
return `${this.title} - ${this.author}`;
};
const myBook = new Book('1984', 'Джордж Оруэлл');
console.log(myBook.getInfo());
Результат:
1984 - Джордж Оруэлл
Пример 2. Наследование через prototype и new:
function Vehicle(type) {
this.type = type;
}
Vehicle.prototype.move = function() {
return `Транспорт ${this.type} движется`;
};
function Car(brand) {
Vehicle.call(this, 'автомобиль');
this.brand = brand;
}
Car.prototype = Object.create(Vehicle.prototype);
Car.prototype.constructor = Car;
Car.prototype.honk = function() {
return `${this.brand} сигналит`;
};
const myCar = new Car('Toyota');
console.log(myCar.move());
console.log(myCar.honk());
Результат:
Транспорт автомобиль движется Toyota сигналит
Пример 3. Оператор new со встроенными конструкторами (Array, Date, RegExp):
const arr = new Array(3); // пустой массив длины 3
console.log(arr.length);
const now = new Date();
console.log(now.toLocaleDateString());
const regex = new RegExp('\\d+', 'g');
console.log(regex.test('abc123'));
Результат (дата может отличаться):
3 01.01.2025 true
Пример 4. Использование new.target в строгом режиме:
'use strict';
function StrictConstr() {
console.log(new.target);
}
const s = new StrictConstr(); // [Function: StrictConstr]
// вызов без new в строгом режиме приведёт к TypeError
Вывод:
[Function: StrictConstr]
Пример 5. Абстрактный конструктор через new.target:
function AbstractClass() {
if (new.target === AbstractClass) {
throw new Error('Нельзя создавать экземпляр абстрактного класса');
}
// инициализация для наследников
}
function Derived() {
AbstractClass.call(this);
}
Derived.prototype = Object.create(AbstractClass.prototype);
try {
const obj = new AbstractClass();
} catch (e) {
console.log(e.message);
}
const d = new Derived();
console.log(d instanceof AbstractClass); // true
Результат:
Нельзя создавать экземпляр абстрактного класса true
Пример 6. Фабрика одиночек (Singleton) с перезаписью конструктора:
function Singleton() {
const instance = this;
Singleton = function() {
return instance;
};
}
const a = new Singleton();
const b = new Singleton();
console.log(a === b); // true
Результат:
true
Пример 7. Symbol.species и new в подклассах:
class MyArray extends Array {
static get [Symbol.species]() { return Array; }
}
const myArr = new MyArray(1, 2, 3);
const mapped = myArr.map(x => x * 2);
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true
Результат:
false true