Мета-программирование - JavaScript | MDN

This page was translated from English by the community. Learn more and join the MDN Web Docs community.

View in English Always switch to English

Мета-программирование

С приходом ECMAScript 2015, в JavaScript введены объекты Proxy и Reflect, позволяющие перехватить и переопределить поведение фундаментальных процессов языка (таких как поиск свойств, присвоение, итерирование, вызов функций и так далее). С помощью этих двух объектов вы можете программировать на мета уровне JavaScript.

Объекты Proxy

Введённый в ECMAScript 6, объект Proxy позволяет перехватить и определить пользовательское поведение для определённых операций. Например, получение свойства объекта:

js
var handler = {
  get: function (target, name) {
    return name in target ? target[name] : 42;
  },
};
var p = new Proxy({}, handler);
p.a = 1;
console.log(p.a, p.b); // 1, 42

Объект Proxy определяет target (в данном случае новый пустой объект) и handler - объект в котором реализована особая функция-ловушка get. "Проксированный" таким образом объект, при доступе к его несуществующему свойству вернёт не undefined, а числовое значение 42.

Дополнительные примеры доступны в справочнике Proxy.

Терминология

В разговоре о функциях объекта Proxy применимы следующие термины:

handler (обработчик)

Объект - обёртка, содержащий в себе функции-ловушки.

ловушки (traps)

Методы, реализующие доступ к свойствам. В своей концепции они аналогичны методам перехвата(hooking) в операционных системах.

цель (target)

Объект, который оборачивается в Proxy. Часто используется лишь как внутреннее хранилище для Proxy объекта. Проверка на нарушение ограничений (invariants), связанных с нерасширяемостью объекта или неконфигурируемыми свойствами объекта производится для конкретной цели.

неизменяемые ограничения (дословно Invariants - те что остаются неизменными)

Некоторые особенности поведения объекта, которые должны быть сохранены при реализации пользовательского поведения названы invariants. Если в обработчике нарушены такие ограничения, будет выброшена ошибка TypeError.

Обработчики и ловушки

В следующей таблице перечислены ловушки, доступные для использования в объекте Proxy. Смотрите подробные объяснения и примеры в документации.

Отзываемый Proxy

Метод Proxy.revocable() создаёт отзываемый объект Proxy. Такой прокси объект может быть отозван функцией revoke, которая отключает все ловушки-обработчики. После этого любые операции над прокси объектом вызовут ошибку TypeError.

js
var revocable = Proxy.revocable(
  {},
  {
    get: function (target, name) {
      return "[[" + name + "]]";
    },
  },
);
var proxy = revocable.proxy;
console.log(proxy.foo); // "[[foo]]"

revocable.revoke();

console.log(proxy.foo); // ошибка TypeError
proxy.foo = 1; // снова ошибка TypeError
delete proxy.foo; // опять TypeError
typeof proxy; // "object", для метода typeof нет ловушек

Рефлексия

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

Reflect помогает при пересылке стандартных операций из обработчика к целевому объекту.

Например, метод Reflect.has() это тот же оператор in но в виде функции:

js
Reflect.has(Object, "assign"); // true

Улучшенная функция apply

В ES5 обычно используется метод Function.prototype.apply() для вызова функции в определённом контексте (с определённым this) и с параметрами, заданными в виде массива (или массива-подобного объекта).

js
Function.prototype.apply.call(Math.floor, undefined, [1.75]);

С методом Reflect.apply эта операция менее громоздка и более понятна:

js
Reflect.apply(Math.floor, undefined, [1.75]);
// 1;

Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111]);
// "hello"

Reflect.apply(RegExp.prototype.exec, /ab/, ["confabulation"]).index;
// 4

Reflect.apply("".charAt, "ponies", [3]);
// "i"

Проверка успешности определения нового свойства

Метод Object.defineProperty, в случае успеха операции, возвращает объект, а при неудаче вызывает ошибку TypeError. Из-за этого определение свойств требует обработки блоком try...catch для перехвата возможных ошибок. Метод Reflect.defineProperty, в свою очередь, возвращает успешность операции в виде булева значения, благодаря чему возможно использование простого if...else условия:

js
if (Reflect.defineProperty(target, property, attributes)) {
  // успех
} else {
  // что-то пошло не так
}