Чтобы проверить является ли функция конструкторной, нужно посмотреть есть ли у нее поле [[Construct]]. Нет ни единого варианта проверить это напрямую.
Но есть два способа, которые мне известны:
- Проверить поля, которые мы можем просмотреть с помощью кода js (быстрее).
- Проверить поле
[[Construct]], используя интерфейсы, которые это делают (медленнее).
К первому типу относится реализация prototype.js, ко второму newTarget.js, bind.js, proxy.js и reflect.js.
Также существует одна весомая разница; при проверке в newTarget.js, bind.js, proxy.js и reflect.js мы используем [[Construct]] поле, которое вызывается через операцию Construct для создания объекта, чтобы убедиться, что объект создан, тем самым это докажет, что функция является конструкторной, но в этом и опасность если произойдет ошибка в алгоритме [[Construct]] проверяемая функция не пройдет проверку.
- [[Construct]] для встроенных функций (пример: Object, Function, Proxy и т.д.)
- [[Construct]] для функций определенных внутри кода ECMAScript (это функции объявленные программистом на js)
- [[Construct]] для привязанных функций (функции созданные через Function.prototype.bind)
- [[Construct]] для прокси объектов (функции созданные через Proxy)
Первый тип же более безопасен он не запускает никакие функции, а просто смотрит наличие полей, которые должны быть у функции конструктора, в этом тоже есть своя опасность, так как отсутствие некоторых полей попросту сломает проверку.
Но есть Proxy! В чем же разница между вариантами newTarget.js, bind.js, reflect.js и proxy.js? Разница последнего в том, что когда происходит вызов [[Construct]], применение происходит не у тестируемой функции, а у обертки proxy, которая имеет собственное поле [[Construct]]. Спецификация определяет разные алгоритмы для обычного функционального объекта и объекта proxy с ловушкой construct. Классические вызовы обычных функциональных объектов через вызов оператора new будут проверять поле [[Construct]] и если его нет - будет выбрасываться ошибка, к proxy это правило также применяется, однако тут у proxy есть преимущество и все из-за того что происходит вызов другого [[Construct]], который впоследствии вызовет специальный метод-ловушку construct у proxy (преимущество в том что тестируемая функция может содержать ошибки внутри себя; через proxy можно не запускать тестируемую функцию, тем самым избегая ненужной нам ошибки, которая появляется в других способах).
Вывод: Самый рабочий и безотказный вариант с Proxy, он отрабатывает по полю [[Construct]] проверяемого значения и в случае если значение не оказывается конструктором выбрасывает ошибку. Первый вариант с просмотром ключевых свойств быстрый, но минус в том что можно подделать поля и проверка будет взламываемой, что плохо.