Я слышал об этих методах и имел общее представление о них, но до сих пор не использовал их.

Из МДН:

Объект Прокси позволяет создать прокси для другого объекта, который может перехватывать и переопределять основные операции для этого объекта.

Reflect — это встроенный объект, предоставляющий методы для перехватываемых операций JavaScript. Методы такие же, как у обработчиков прокси.

Эти методы позволяют вам перехватывать и вносить изменения в то, как что-то может работать, будь то объект, определенный в вашем коде, или встроенный метод, который поставляется с браузером через веб-API.

Вариант использования в реальном мире

В последнее время я углублялся в мир политик безопасности контента (CSP), и возникла проблема, когда сторонний скрипт, размещенный на CDN, внедрил тег стиля.

Проблема заключалась в том, что заголовок CSP имеет одноразовый номер, примененный к директиве style-src, сторонний скрипт, внедряющий тег стиля, будет отклонен из-за неприменения одноразового номера, даже если сам скрипт имеет применяется одноразовый номер. Хотя директива script-src может использовать strict-dynamic, эквивалента для внедрения CSS через JavaScript не существует.

Теперь мы могли бы легко использовать метод грубой силы, сделать локальную копию и внести изменения в исходный код самостоятельно, но это сопряжено с необходимостью поддерживать скрипт в актуальном состоянии. Существуют плагины для веб-пакетов, которые могут справиться с этим во время сборки.

Желая избежать этого шага, коллега сделал отличное предложение о том, можем ли мы переопределить что-то, чтобы заставить его работать хорошо, и добавить одноразовый номер прогамматически.

Квазистандартом для большинства библиотек CSS-in-JS является проверка глобального __webpack_nonce__ и применение его ко всем внедренным тегам стиля.

Давайте посмотрим, что мы можем сделать, чтобы перехватить создание тега стиля и добавить одноразовый номер, если доступен глобальный элемент __webpack_nonce__.

document.createElement = new Proxy(document.createElement, {
  apply(target, thisArg, args) {
    const elem = Reflect.apply(target, thisArg, args);

    if (args[0] === "style" && !!window.__webpack_nonce__) {
      elem.nonce = window.__webpack_nonce__;
    }

    return elem;
  },
});

Решение довольно компактное и использует Proxy & Reflect для исправления необходимых нам изменений и передачи к базовому методу для всего остального. По сути, это безопасный патч, который ничего не сломает.

Как это работает?

Мы проксируем document.createElement, так сторонний скрипт внедряет свои стили.

document.createElement = new Proxy(document.createElement, {});

Использование прокси означает, что мы указываем, какой аргумент обработчика мы хотим перехватить, в нашем случае это apply, так как это вызов метода, например. document.createElement("стиль").

apply(target, thisArg, args) {}

Затем мы используем Reflect.apply для обратного вызова исходного встроенного метода, не требуя обработки переменной tmp!

const elem = Reflect.apply(target, thisArg, args);

После его вызова мы проверяем, создает ли он тег стиля и установлен ли глобальный элемент __webpack_nonce__. Если это так, мы применяем к нему свойство nonce и возвращаем элемент.

if (args[0] === "style" && !!window.__webpack_nonce__) {
  elem.nonce = window.__webpack_nonce__;
}

Производительность

Вашей следующей мыслью может быть:

document.createElement вызывается много раз в каждом базовом фреймворке, так каковы накладные расходы на перехват этого?

Мы отключаем его раньше, если он не соответствует нашим конкретным критериям, но это все равно увеличивает нагрузку на любой вызов веб-API createElement.

Я провел несколько тестов производительности с вызовами Proxy и Reflect и без них на двух машинах с Chrome 107 и Firefox 106.

Я провел тесты производительности, используя тест Append 1000 rows в ключевом репозитории js-framework-benchmark.

Статистика машины следующая:

Это только приблизительные цифры, а не настоящий научный прогон, но он показывает, что накладные расходы на добавление 1000 строк довольно малы. Так что обмен в нашем случае удобен.

Заключение

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