Метапрограммирование: Proxy, Reflect, Symbol

Proxy

Прокси — обёртка вокруг объекта, которая «по умолчанию» перенаправляет операции на объект, но может их перехватывать. Проксировать можно любой объект, включая классы и функции.

let proxy = new Proxy(target, {
  /* ловушки (traps) */
});

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

Можно перехватывать:

  • чтение (get), запись (set), удаление (deleteProperty) свойства — даже несуществующего;
  • вызов функции (apply);
  • оператор new (ловушка construct);
  • и многие другие операции.

Reflect

Reflect — API, созданное как дополнение к Proxy. Для любой ловушки из Proxy есть метод в Reflect с теми же аргументами. Его используют, когда нужно перенаправить операцию на оригинальный объект.

let proxy = new Proxy(target, {
  get(obj, prop, receiver) {
    return Reflect.get(obj, prop, receiver); // перенаправляем на оригинал
  },
});

Symbol

Символ — примитивный тип данных для создания уникальных идентификаторов. Создаётся вызовом Symbol(), в который можно передать описание (имя).

const a = Symbol("id");
const b = Symbol("id");
a === b; // false — даже с одинаковым описанием это разные символы

Глобальный реестр: Symbol.for(key) возвращает (или создаёт) глобальный символ с key в качестве имени. Повторные вызовы с тем же аргументом возвращают тот же символ.

Symbol.for("id") === Symbol.for("id"); // true

Символы скрыты не на 100%: получить символьные ключи объекта можно через Object.getOwnPropertySymbols(obj) и Reflect.ownKeys(obj). Но большинство библиотек и синтаксических конструкций их не используют.

Well-Known Symbols (системные): множество «системных» символов, используемых внутри самого JavaScript (например, Symbol.iterator, Symbol.asyncIterator), которыми можно настраивать поведение объектов.