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

Функция-конструктор — обычная функция, вызываемая с оператором new для создания нового объекта. Оператор new создаёт пустой объект, привязывает к нему this и возвращает его.

Зачем нужно

Конструкторы — базовый механизм создания объектов с общей структурой и поведением. До ES6-классов это был единственный способ. Понимание конструкторов объясняет, как работают классы «под капотом».

Где используется

Создание экземпляров, паттерн Factory, встроенные конструкторы (Date, RegExp, Error), legacy-код, понимание классов.

Предпосылки

this, Прототипы

Как работает new

function User(name, age) {
  // 1. Создаётся пустой объект: this = {}
  // 2. Устанавливается прототип: this.__proto__ = User.prototype
  this.name = name;  // 3. Добавляются свойства
  this.age = age;
  // 4. Возвращается this (неявно)
}

const ivan = new User('Иван', 25);
console.log(ivan);        // User { name: 'Иван', age: 25 }
console.log(ivan instanceof User); // true

Пошагово

// new User('Иван', 25) эквивалентно:
function simulateNew(Constructor, ...args) {
  const obj = Object.create(Constructor.prototype); // шаг 1-2
  const result = Constructor.apply(obj, args);       // шаг 3
  return result instanceof Object ? result : obj;    // шаг 4
}

const user = simulateNew(User, 'Иван', 25);

Конструктор с prototype

function Animal(name, sound) {
  this.name = name;
  this.sound = sound;
}

// Методы в prototype — общие для всех экземпляров
Animal.prototype.speak = function {
  return `${this.name} говорит: ${this.sound}`;
};

Animal.prototype.toString() = function {
  return `[Animal: ${this.name}]`;
};

const cat = new Animal('Мурка', 'Мяу');
const dog = new Animal('Бобик', 'Гав');

console.log(cat.speak); // "Мурка говорит: Мяу"
console.log(dog.speak); // "Бобик говорит: Гав"

// Один метод на все экземпляры
console.log(cat.speak === dog.speak); // true

Return из конструктора

// Возврат объекта — заменяет this
function Strange() {
  this.a = 1;
  return { b: 2 }; // this отбрасывается!
}
console.log(new Strange); // { b: 2 }

// Возврат примитива — игнорируется
function Normal() {
  this.a = 1;
  return 42; // игнорируется
}
console.log(new Normal); // Normal { a: 1 }

// return без значения — возвращается this
function AlsoNormal() {
  this.a = 1;
  return;
}
console.log(new AlsoNormal); // AlsoNormal { a: 1 }

instanceof

function Car(brand) {
  this.brand = brand;
}

const tesla = new Car('Tesla');

console.log(tesla instanceof Car);    // true
console.log(tesla instanceof Object); // true (цепочка прототипов)

// instanceof проверяет Car.prototype в цепочке tesla
console.log(Object.getPrototypeOf(tesla) === Car.prototype); // true

Конструктор vs Фабричная функция

Конструктор

function UserConstructor(name) {
  this.name = name;
}
UserConstructor.prototype.greet = function {
  return `Привет, ${this.name}`;
};

const u1 = new UserConstructor('Иван');
console.log(u1 instanceof UserConstructor); // true

Фабрика

function createUser(name) {
  return {
    name,
    greet {
      return `Привет, ${name}`; // замыкание, не this
    }
  };
}

const u2 = createUser('Мария');
console.log(u2 instanceof createUser); // false — нет прототипной связи

Сравнение

Аспект Конструктор Фабрика
Вызов new Fn fn
this Автоматический Не используется
instanceof Работает Не работает
Прототип Общие методы Копия методов
Приватность Нужен # или _ Замыкание
Забыл new Баг (без class) Нет проблемы
Память Эффективнее (prototype) Больше (копии методов)

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

function SafeUser(name) {
  // Проверка: вызвана ли с new
  if (!(this instanceof SafeUser)) {
    return new SafeUser(name); // автоматический new
  }
  this.name = name;
}

const a = new SafeUser('Иван');
const b = SafeUser('Мария'); // тоже работает
console.log(a.name, b.name); // "Иван" "Мария"

// new.target (ES6) — более надёжный способ
function ModernUser(name) {
  if (!new.target) {
    throw new Error('Используйте new ModernUser');
  }
  this.name = name;
}

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

// Не рекомендуется для примитивов — используйте литералы
const str1 = new String('hello'); // объект-обёртка
const str2 = 'hello';             // примитив
console.log(typeof str1); // "object"
console.log(typeof str2); // "string"
console.log(str1 == str2);  // true (приведение)
console.log(str1 === str2); // false!

// Полезные встроенные конструкторы
const date = new Date();
const regex = new RegExp('\\d+', 'g');
const error = new Error('Что-то пошло не так');
const map = new Map();
const set = new Set();

Конструктор и класс — одно и то же

class MyClass {
  constructor(x) { this.x = x; }
  method { return this.x; }
}

// Эквивалент:
function MyFunc(x) { this.x = x; }
MyFunc.prototype.method = function { return this.x; };

// Проверка
console.log(typeof MyClass); // "function"
console.log(MyClass.prototype.constructor === MyClass); // true

Частые ошибки

1. Забытый new

function User(name) {
  this.name = name; // this === window без new!
}

// const u = User('Иван'); // window.name = 'Иван' (загрязнение глобала)
// console.log(u); // undefined

// Класс защищает от этого:
// class User { constructor(name) { this.name = name; } }
// User('Иван'); // TypeError: cannot be invoked without 'new'

2. Методы в конструкторе вместо prototype

// Плохо — новая функция для каждого экземпляра
function Bad(name) {
  this.name = name;
  this.greet = function { return this.name; }; // копия!
}

const a = new Bad('A');
const b = new Bad('B');
console.log(a.greet === b.greet); // false — расход памяти

// Хорошо — общий метод в prototype
function Good(name) {
  this.name = name;
}
Good.prototype.greet = function { return this.name; };

const c = new Good('C');
const d = new Good('D');
console.log(c.greet === d.greet); // true

3. Перезапись prototype после создания экземпляров

function Foo() {}
const old = new Foo();

Foo.prototype = { newMethod {} };
const newer = new Foo();

console.log(old instanceof Foo);  // false! — старый прототип
console.log(newer instanceof Foo); // true

Практика

  1. Создай конструктор Car(brand, model, year) с методами в prototype
  2. Напиши свою реализацию new (функция myNew(Constructor, ...args))
  3. Сравни конструктор и фабричную функцию по памяти и возможностям
  4. Добавь проверку new.target в конструктор
  5. Создай иерархию: VehicleCarElectricCar через конструкторы и prototype

Связанные темы

Ресурсы