Функции-конструкторы и оператор new

объекты≈ 7 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

Один объект удобно написать литералом. Но когда таких объектов нужно двадцать и все одной формы, копипаста надоедает — тогда в дело идут функция-конструктор и оператор new.

Разделы темы:·

Литерал { } отлично описывает один объект. Проблема начинается, когда объектов много и они устроены одинаково: пользователь, ещё пользователь, ещё сорок пользователей. Каждый раз выписывать одни и те же поля — скучно и легко ошибиться.

Функция-конструктор — это чертёж. Обычная функция, внутри которой описано, какие поля будут у объекта, а оператор new — команда «изготовь по этому чертежу одну штуку».

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

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

ШагЧто делает new
1Создаёт новый пустой объект
2Записывает его в this
3Выполняет тело функции — оно дописывает в this поля
4Возвращает получившийся объект

Первый и четвёртый шаги в коде не видны — они происходят сами. Если дописать их руками, конструктор перестанет быть загадочным:

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Конструктор — не особый сорт функции. Это обычная функция, которую позвали через new. Она же, вызванная без new, — просто функция.

Если аргументов нет, скобки разрешено опустить: new User и new User() сделают одно и то же. Так писать не стоит — скобки подсказывают читателю, что здесь вызов.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Внутри конструктора this — это и есть будущий объект, ещё пустой. Всё, что вы ему присвоите, окажется в результате. Присваивать можно не только данные: функция в свойстве — это метод, и при вызове obj.method() её собственный this тоже укажет на объект.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Обратите внимание: return в конструкторе обычно не пишут — объект вернётся сам. Но если написать, у языка есть на этот счёт отдельное правило.

Правило простое: return с объектом подменяет результат целиком, а return с примитивом (или без значения) просто игнорируется — вернётся this.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Конструкторы принято называть с большой буквы: User, Point, Counter. Движку всё равно — new сработает и с user. Это договорённость между людьми: заглавная буква говорит «меня зовут через new», и по одному имени в чужом коде видно, вызов это или изготовление объекта.

А вот чего договорённостями не обойти: стрелочная функция конструктором быть не может. У неё нет собственного this, и new с ней падает.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Внутри любой функции доступно специальное значение new.target. При обычном вызове оно равно undefined, при вызове через new — самой функции. Это единственный честный способ узнать изнутри, как вас позвали.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Чаще всего его используют для подстраховки: если new забыли, конструктор дописывает его сам. Приём известный, но злоупотреблять им не стоит: код, который работает и так и эдак, читать труднее. Иногда полезнее наоборот — бросить понятную ошибку.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Функции-конструкторы никуда не делись, но в современном коде ту же задачу обычно решает class. Механика под капотом та же самая: new делает свои четыре шага, this означает новый объект. Меняется только запись — тело конструктора переезжает в метод constructor, а остальные методы пишутся рядом. Эту запись подробно разбирает статья «Классы: синтаксис».

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Есть и приятная разница: в нашем Counter метод inc копировался в каждый объект, а методы класса лежат в одном общем месте — прототипе. Что это такое, разберём в главе «Прототипы и классы», начиная со статьи «Прототипное наследование»; пока достаточно знать, что классы — не новый механизм, а удобная обёртка вокруг уже знакомого.

  • new Имя() создаёт пустой объект, кладёт его в this, выполняет тело функции и возвращает получившийся объект
  • Внутри конструктора this — это будущий объект; функции в его свойствах становятся методами
  • return обычно не нужен: с объектом он подменяет результат, с примитивом игнорируется
  • Заглавная буква в имени — соглашение, а не правило языка; стрелочная функция конструктором быть не может
  • new.target равен самой функции при вызове через new и undefined без него; class — та же механика в более короткой записи

1/3Функция-конструктор заканчивается строкой return 42. Что вернёт вызов через new?

2/3Чему равен new.target внутри функции, вызванной обычным способом, без new?

3/3Что означает заглавная буква в имени функции-конструктора?

Аккумулятор

Напишите функцию-конструктор Accumulator(startValue). Она создаёт объект со свойством value (в него попадает аргумент) и методом add(n), который прибавляет n к value.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Начальное значение кладём в this.value. Метод — обычная функция в свойстве объекта: её вызывают как acc.add(5), поэтому внутри неё this тоже указывает на наш объект.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Метод не обязан ничего возвращать — он меняет состояние объекта, а результат читают из acc.value.

Конструктор, которому всё равно

Допишите Point так, чтобы объект получался и с new, и без него. Само тело конструктора менять не нужно — хватит одной строки в начале.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Проверяем new.target: если он пустой, значит функцию позвали обычным вызовом — тогда зовём сами себя правильно и возвращаем результат.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Возврат объекта из конструктора здесь как раз уместен: return с объектом подменяет результат, поэтому и вызов через new Point(...) отработает без сюрпризов.