Skip to content

Instantly share code, notes, and snippets.

@dSalieri
Last active November 21, 2023 08:56
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dSalieri/80b982e1fd77072c9ccb44958c7579c4 to your computer and use it in GitHub Desktop.
Save dSalieri/80b982e1fd77072c9ccb44958c7579c4 to your computer and use it in GitHub Desktop.
IsConstructor попытка реализовать проверку на конструктор

Чтобы проверить является ли функция конструкторной, нужно посмотреть есть ли у нее поле [[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]] проверяемого значения и в случае если значение не оказывается конструктором выбрасывает ошибку. Первый вариант с просмотром ключевых свойств быстрый, но минус в том что можно подделать поля и проверка будет взламываемой, что плохо.

function IsConstructor(value) {
try {
new (value.bind());
}
catch (e) {
return false;
}
return true;
}
function IsConstructor(value) {
try {
new value;
} catch (e) {
return false;
}
return true;
}
function IsConstructor(value) {
if (typeof value !== "function") return false;
return value.prototype && value.prototype.constructor === value;
}
function IsConstructor(value) {
try {
new new Proxy(value, {construct() { return {} }});
}
catch(e) {
return false;
}
return true;
}
function IsConstructor(value) {
try {
Reflect.construct(value, []);
}
catch (e) {
return false;
}
return true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment