Что делает новый оператор 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()
функции!
Есть и другие способы реализовать это, и вы можете добавить больше проверок, пожалуйста, оставьте комментарий, если у вас есть другие версии.