Обычный синтаксис {...}
позволяет создать только один объект. Но зачастую нам нужно создать множество похожих, однотипных объектов, таких как пользователи, элементы меню и так далее.
Это можно сделать при помощи функции-конструктора и оператора "new"
.
Функция-конструктор
Функции-конструкторы технически являются обычными функциями. Но есть два соглашения:
- Имя функции-конструктора должно начинаться с большой буквы.
- Функция-конструктор должна выполняться только с помощью оператора
"new"
.
Например:
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
alert(user.name); // Jack
alert(user.isAdmin); // false
Когда функция вызывается как new User(...)
, происходит следующее:
- Создаётся новый пустой объект, и он присваивается
this
. - Выполняется тело функции. Обычно оно модифицирует
this
, добавляя туда новые свойства. - Возвращается значение
this
.
Другими словами, new User(...)
делает что-то вроде:
function User(name) {
// this = {}; (неявно)
// добавляет свойства к this
this.name = name;
this.isAdmin = false;
// return this; (неявно)
}
Таким образом, let user = new User("Jack")
возвращает тот же результат, что и:
let user = {
name: "Jack",
isAdmin: false
};
Теперь, если нам будет необходимо создать других пользователей, мы можем просто вызвать new User("Ann")
, new User("Alice")
и так далее. Данная конструкция гораздо удобнее и читабельнее, чем многократное создание литерала объекта.
Это и является основной целью конструкторов – реализовать код для многократного создания однотипных объектов.
Давайте ещё раз отметим – технически любая функция (кроме стрелочных функций, поскольку у них нет this
) может использоваться в качестве конструктора. Его можно запустить с помощью new
, и он выполнит выше указанный алгоритм. Подобные функции должны начинаться с заглавной буквы – это общепринятое соглашение, чтобы было ясно, что функция должна вызываться с помощью «new».
Если в нашем коде присутствует большое количество строк, создающих один сложный объект, то мы можем обернуть их в функцию-конструктор, которая будет немедленно вызвана, вот так:
// создаём функцию и сразу же вызываем её с помощью new
let user = new function() {
this.name = "John";
this.isAdmin = false;
// ...другой код для создания пользователя
// возможна любая сложная логика и инструкции
// локальные переменные и так далее
};
Такой конструктор не может быть вызван снова, так как он нигде не сохраняется, просто создаётся и тут же вызывается. Таким образом, этот трюк направлен на инкапсуляцию кода, который создаёт отдельный объект, без возможности повторного использования в будущем.
Проверка на вызов в режиме конструктора: new.target
Синтаксис из этого раздела используется крайне редко. Вы можете пропустить его, если не хотите углубляться в детали языка.
Используя специальное свойство new.target
внутри функции, мы можем проверить, вызвана ли функция при помощи оператора new
или без него.
В случае обычного вызова функции new.target
будет undefined
. Если же она была вызвана при помощи new
, new.target
будет равен самой функции.
function User() {
alert(new.target);
}
// без "new":
User(); // undefined
// с "new":
new User(); // function User { ... }
Это можно использовать внутри функции, чтобы узнать, была ли она вызвана при помощи new
, «в режиме конструктора», или без него, «в обычном режиме».
Также мы можем сделать, чтобы вызовы с new
и без него делали одно и то же:
function User(name) {
if (!new.target) { // в случае, если вы вызвали меня без оператора new
return new User(name); // ...я добавлю new за вас
}
this.name = name;
}
let john = User("John"); // переадресовывает вызов на new User
alert(john.name); // John
Такой подход иногда используется в библиотеках, чтобы сделать синтаксис более гибким. Чтобы люди могли вызывать функцию с new
и без него, и она все ещё могла работать.
Впрочем, вероятно, это не очень хорошая практика использовать этот трюк везде, так как отсутствие new
может ввести разработчика в заблуждение. С new
мы точно знаем, что создаётся новый объект.
Возврат значения из конструктора, return
Обычно конструкторы не имеют оператора return
. Их задача – записать все необходимое в this
, и это автоматически становится результатом.
Но если return
всё же есть, то применяется простое правило:
- При вызове
return
с объектом, вместоthis
вернётся объект. - При вызове
return
с примитивным значением, оно проигнорируется.
Другими словами, return
с объектом возвращает этот объект, во всех остальных случаях возвращается this
.
К примеру, здесь return
замещает this
, возвращая объект:
function BigUser() {
this.name = "John";
return { name: "Godzilla" }; // <-- возвращает этот объект
}
alert( new BigUser().name ); // Godzilla, получили этот объект
А вот пример с пустым return
(или мы могли бы поставить примитив после return
, неважно):
function SmallUser() {
this.name = "John";
return; // <-- возвращает this
}
alert( new SmallUser().name ); // John
Обычно у конструкторов отсутствует return
. Здесь мы упомянули особое поведение с возвращаемыми объектами в основном для полноты картины.
Кстати, мы можем не ставить круглые скобки после new
:
let user = new User; // <-- без скобок
// то же, что и
let user = new User();
Пропуск скобок считается плохой практикой, но просто чтобы вы знали, такой синтаксис разрешён спецификацией.
Создание методов в конструкторе
Использование конструкторов для создания объектов даёт большую гибкость. Функции-конструкторы могут иметь параметры, определяющие, как создавать объект и что в него записывать.
Конечно, мы можем добавить к this
не только свойства, но и методы.
Например, new User(name)
ниже создаёт объект с заданным name
и методом sayHi
:
function User(name) {
this.name = name;
this.sayHi = function() {
alert( "Меня зовут: " + this.name );
};
}
let john = new User("John");
john.sayHi(); // Меня зовут: John
/*
john = {
name: "John",
sayHi: function() { ... }
}
*/
Для создания сложных объектов есть и более продвинутый синтаксис – классы, который мы рассмотрим позже.
Итого
- Функции-конструкторы или просто конструкторы, являются обычными функциями, но существует общепринятое соглашение именовать их с заглавной буквы.
- Функции-конструкторы следует вызывать только с помощью
new
. Такой вызов подразумевает создание пустогоthis
в начале и возврат заполненного в конце.
Мы можем использовать конструкторы для создания множества похожих объектов.
JavaScript предоставляет функции-конструкторы для множества встроенных объектов языка: таких как Date
, Set
, и других, которые нам ещё предстоит изучить.
В этой главе мы рассмотрели только основы объектов и конструкторов. Данная информация необходима нам для дальнейшего изучения типов данных и функций в последующих главах.
Как только мы с ними разберёмся, мы вернёмся к объектам для более детального изучения в главах Прототипы, наследование и Классы.