Skip to content

Latest commit

 

History

History
231 lines (158 loc) · 11.7 KB

File metadata and controls

231 lines (158 loc) · 11.7 KB

Конструктор, оператор "new"

Обычный синтаксис {...} позволяет создать только один объект. Но зачастую нам нужно создать множество похожих, однотипных объектов, таких как пользователи, элементы меню и так далее.

Это можно сделать при помощи функции-конструктора и оператора "new".

Функция-конструктор

Функции-конструкторы технически являются обычными функциями. Но есть два соглашения:

  1. Имя функции-конструктора должно начинаться с большой буквы.
  2. Функция-конструктор должна выполняться только с помощью оператора "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(...), происходит следующее:

  1. Создаётся новый пустой объект, и он присваивается this.
  2. Выполняется тело функции. Обычно оно модифицирует this, добавляя туда новые свойства.
  3. Возвращается значение 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".

Если в нашем коде присутствует большое количество строк, создающих один сложный объект, то мы можем обернуть их в анонимную функцию-конструктор, которая будет немедленно вызвана, вот так:

```js
// создаём функцию и сразу же вызываем её с помощью 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`:

```js
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, и других, которые нам ещё предстоит изучить.

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

Как только мы с ними разберёмся, мы вернёмся к объектам для более детального изучения в главах <info:prototypes> и <info:classes>.