Что делает новый оператор JavaScript под капотом?

Концепция оператора new взята из объектно-ориентированных языков на основе классов, таких как C ++ и Java. Он используется для создания экземпляра или объекта класса и в основном выполняет две функции:
Создание экземпляра - выделение пространства в памяти для создаваемого объекта и
Инициализация - выполнение метода конструктора и инициализация объекта (например его переменных-членов и функций-членов).

Для JavaScript это не язык на основе классов, как Java, он основан на прототипах, поэтому его оператор new выполняет свои очень специфические функции и сильно отличается от оператора new в Java.
По данным MDN:

Оператор new позволяет разработчикам создать экземпляр определенного пользователем типа объекта или одного из встроенных типов объектов, который имеет функцию конструктора.

Например, мы создаем функцию-конструктор ниже и используем оператор new для создания экземпляра этой функции-конструктора:

function Employee(name) {
  this.name = name;
}

const employee = new Employee('Mike');

Employee() - это обычная функция в JS, и когда мы используем перед ней оператор new, он становится «функцией-конструктором». Теперь объект конструктора создан, и его ссылка назначается постоянной переменной employee.

Согласно MDN оператор new выполняет следующие четыре действия:

Создает пустой простой объект JavaScript;

Связывает (устанавливает конструктор) этот объект с другим объектом;

Передает вновь созданный объект из шага 1 как контекст this;

Возвращает this, если функция не возвращает объект.

Также,

Создание пользовательского объекта требует двух шагов:

Определите тип объекта, написав функцию.

Создайте экземпляр объекта с new.

Имея официальное определение, давайте сами реализуем оператор new.

Сначала создайте функцию с именем New . Требуется пара параметров, и первый параметр - это переданная функция-конструктор. Мы используем синтаксис rest для сбора остальные параметры в массив с именем args.

function New(constructor, ...args) {

}

Шаг 1. Создает пустой простой объект JavaScript

Давайте создадим пустой объект в теле функции:

function New(constructor, ...args) {
  // 1. Create a new empty object
  const obj = {};
  // const obj = new Object();
  // const obj = Object.create(null);

}

Обратите внимание, что есть три способа создать пустой объект:

const obj = {};
const obj = new Object();
const obj = Object.create(null);

Поэтому я поместил два других в комментарии для справки.

Шаг 2. Связывает (устанавливает конструктор) этот объект с другим объектом.

Это можно сделать с помощью одного оператора присваивания:

function New(constructor, ...args) {
  // 1. Create a new empty object
  const obj = {};
  // const obj = new Object();
  // const obj = Object.create(null);

  // 2. Assign the constructor’s prototype property to the new empty object’s __protp__ property
  obj.__proto__ = constructor.prototype;
  // Object.setPrototypeOf(obj, constructor.prototype);

}

Мы устанавливаем ссылку prototype конструктора на свойство __proto__ нового объекта. Здесь вы также можете использовать Object.setPrototypeOf() , и это обычно рекомендуется. Ниже этих двух строк выполняется то же самое:

obj.__proto__ = constructor.prototype;
Object.setPrototypeOf(obj, constructor.prototype);

Этот единственный оператор присваивания устанавливает цепочку прототипов для объекта obj. Это важно, потому что это доказывает, что объекты в JS полагаются на прототип и цепочку прототипов. Это полностью отличается от своего «брата» Java (JS должен был называться «LiveScript» при создании в 1990-х, затем он был переименован в «JavaScript», потому что в то время появилась Java, и она была самым популярным языком программирования).

Шаг 3. Передает вновь созданный объект из шага 1 как контекст this.

Теперь нам нужно выполнить функцию-конструктор, переданную нашей функции New() :

function New(constructor, ...args) {
  // 1. Create a new empty object
  const obj = {};
  // const obj = new Object();
  // const obj = Object.create(null);

  // 2. Assign the constructor’s prototype property to the new empty object’s __protp__ property
  obj.__proto__ = constructor.prototype;
  // Object.setPrototypeOf(obj, constructor.prototype);

  // 3. Execute the constructor, set obj as the context of `this` when the constructor runs
  const result = constructor.call(obj, args);

}

Мы делаем это с помощью метода call() и сохраняем результат в постоянной переменной result.

Шаг 4. Возвращает this, если функция не возвращает объект.

Мы проверим, является ли результат вызова конструктора на шаге 3 типом объекта, а затем вернем его. Если нет, мы вернем obj в качестве нового объекта:

function New(constructor, ...args) {
  // 1. Create a new empty object
  const obj = {};
  // const obj = new Object();
  // const obj = Object.create(null);

  // 2. Assign the constructor’s prototype property to the new empty object’s __protp__ property
  obj.__proto__ = constructor.prototype;
  // Object.setPrototypeOf(obj, constructor.prototype);

  // 3. Execute the constructor, set obj as the context of `this` when the constructor runs
  const result = constructor.call(obj, args);

  // 4. Return the object
  return typeof result === 'object' ? result : obj;
}

Готово, давайте проверим. Мы создаем функцию-конструктор Employee(), и она получает массив параметров:

function Employee(args) {
  this.name = args[0];
  this.title = args[1];
  console.log('constructor, this = ', this);
}

Теперь мы можем использовать нашу собственную New() функцию для создания объекта на основе конструктора Employee():

const employeeA = New(Employee, 'Joe', 27);
console.log('employeeA = ', employeeA);

Запускаем и в консоли видим:

Новый объект employeeA был успешно создан с помощью нашей New() функции!

Есть и другие способы реализовать это, и вы можете добавить больше проверок, пожалуйста, оставьте комментарий, если у вас есть другие версии.