Constructor Pattern

Constructor Pattern — паттерн создания объектов через функцию-конструктор или класс, вызываемые с оператором new, устанавливающий начальное состояние и помещающий методы в прототип.

Зачем нужно

Constructor Pattern — основа объектно-ориентированного JavaScript до ES6. Понимание того, как new работает «под капотом», объясняет механизм прототипного наследования, поведение this в конструкторах и связь между классами (которые являются синтаксическим сахаром над Constructor Pattern + prototype). Это знание необходимо для работы с legacy-кодом и понимания классов ES6.

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

  • Создание множества экземпляров с одинаковой структурой (пользователи, товары, события)
  • Реализация базовых классов и наследования в ES5-коде
  • Понимание работы встроенных конструкторов: new Date, new Map, new Promise
  • Полифиллы и библиотеки, поддерживающие старые браузеры
  • Метапрограммирование: переопределение Symbol.hasInstance для instanceof

Основной контент

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

function Person(name, age) {
  // При вызове с new:
  // 1. Создаётся новый пустой объект {}
  // 2. Его прототип устанавливается в Person.prototype
  // 3. this внутри функции указывает на этот объект
  // 4. Функция выполняется
  // 5. Объект возвращается (если нет явного return объекта)

  this.name = name;
  this.age = age;
}

// Методы на прототипе — одна копия для всех экземпляров
Person.prototype.greet = function {
  return `Привет, я ${this.name}`;
};

Person.prototype.toString() = function {
  return `Person(${this.name}, ${this.age})`;
};

const ivan = new Person('Иван', 30);
const maria = new Person('Мария', 25);

console.log(ivan.greet);  // 'Привет, я Иван'
console.log(ivan instanceof Person); // true
console.log(ivan.constructor === Person); // true

// Прототип разделяется:
console.log(ivan.greet === maria.greet); // true — одна функция!

Эмуляция new вручную

function myNew(Constructor, ...args) {
  // Создаём объект с прототипом Constructor.prototype
  const obj = Object.create(Constructor.prototype);
  // Вызываем конструктор с this = obj
  const result = Constructor.apply(obj, args);
  // Если конструктор вернул объект — используем его, иначе obj
  return (typeof result === 'object' && result !== null) ? result : obj;
}

const person = myNew(Person, 'Иван', 30);
console.log(person.greet); // 'Привет, я Иван'

Наследование через Constructor Pattern (ES5-стиль)

function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function {
  return `${this.name} издаёт звук`;
};

function Dog(name, breed) {
  Animal.call(this, name); // вызов родительского конструктора
  this.breed = breed;
}

// Устанавливаем цепочку прототипов
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // восстанавливаем ссылку

Dog.prototype.speak = function {
  return `${this.name} лает`;
};

const rex = new Dog('Рекс', 'Лабрадор');
console.log(rex.speak);           // 'Рекс лает'
console.log(rex instanceof Animal); // true
console.log(rex instanceof Dog);    // true

Constructor Pattern vs class (ES6)

// ES6 class — синтаксический сахар над тем же механизмом
class PersonES6 {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  greet {
    return `Привет, я ${this.name}`;
  }
}

// Под капотом то же самое:
console.log(typeof PersonES6); // 'function'
console.log(PersonES6.prototype.greet === PersonES6.prototype.greet); // true

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

  • Вызов конструктора без new: без new this указывает на глобальный объект (или undefined в strict mode) — свойства засоряют глобальное пространство. Защита: if (!(this instanceof Person)) return new Person(name, age).
  • Методы в теле конструктора: this.greet = function {} внутри конструктора создаёт новую функцию для каждого экземпляра — лишний расход памяти. Методы нужно размещать на prototype.
  • Забыть восстановить constructor: после Dog.prototype = Object.create(Animal.prototype) нужно явно Dog.prototype.constructor = Dog, иначе new Dog.constructor === Animal.

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

Ресурсы