From 074785cea106179cb3305637055ab0a009ca74f2 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:42:52 -0500 Subject: initial commit --- .../javascript/guide/meta_programming/index.html | 265 +++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 files/ru/web/javascript/guide/meta_programming/index.html (limited to 'files/ru/web/javascript/guide/meta_programming/index.html') diff --git a/files/ru/web/javascript/guide/meta_programming/index.html b/files/ru/web/javascript/guide/meta_programming/index.html new file mode 100644 index 0000000000..7ab4762677 --- /dev/null +++ b/files/ru/web/javascript/guide/meta_programming/index.html @@ -0,0 +1,265 @@ +--- +title: Мета-программирование +slug: Web/JavaScript/Guide/Meta_programming +tags: + - Guide + - JavaScript + - Meta + - Meta programming + - Proxy + - Reflect + - Метапрограммирование +translation_of: Web/JavaScript/Guide/Meta_programming +--- +
{{jsSidebar("JavaScript Guide")}} {{Previous("Web/JavaScript/Guide/Iterators_and_Generators")}}
+ +

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

+ +

Объекты Proxy

+ +

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

+ +
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.

+ +

Дополнительные примеры доступны в справочнике {{jsxref("Proxy")}}.

+ +

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

+ +

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

+ +
+
{{jsxref("Global_Objects/Proxy/handler","handler","","true")}} (обработчик)
+
Объект - обертка, содержащий в себе функции-ловушки.
+
ловушки (traps)
+
Методы, реализующие доступ к свойствам. В своей концепции они аналогичны методам перехвата(hooking) в операционных системах.
+
цель (target)
+
Объект, который оборачивается в Proxy. Часто используется лишь как внутреннее хранилище для Proxy объекта. Проверка на нарушение ограничений (invariants), связанных с нерасширяемостью объекта или неконфигурируемыми свойствами объекта производится для конкретной цели
+
неизменяемые ограничения (дословно Invariants - те что остаются неизменными)
+
Некоторые особенности поведения объекта, которые должны быть сохранены при реализации пользовательского поведения названы invariants. Если в обработчике нарушены такие ограничения, будет выброшена ошибка {{jsxref("TypeError")}}.
+
+ +

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

+ +

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

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Обработчик / ловушкаПерехватываемые методыНеизменяемые ограничения
{{jsxref("Global_Objects/Proxy/handler/getPrototypeOf", "handler.getPrototypeOf()")}}{{jsxref("Object.getPrototypeOf()")}}
+ {{jsxref("Reflect.getPrototypeOf()")}}
+ {{jsxref("Object/proto", "__proto__")}}
+ {{jsxref("Object.prototype.isPrototypeOf()")}}
+ {{jsxref("Operators/instanceof", "instanceof")}}
+
    +
  • метод getPrototypeOf должен вернуть object или null.
  • +
  • если целевой объект target нерасширяем, метод Object.getPrototypeOf(proxy) должен возвращать тот же результат что и Object.getPrototypeOf(target).
  • +
+
{{jsxref("Global_Objects/Proxy/handler/setPrototypeOf", "handler.setPrototypeOf()")}}{{jsxref("Object.setPrototypeOf()")}}
+ {{jsxref("Reflect.setPrototypeOf()")}}
если целевой объект target нерасширяем, значение параметра prototype должно быть равным значению возвращаемому методом Object.getPrototypeOf(target).
{{jsxref("Global_Objects/Proxy/handler/isExtensible", "handler.isExtensible()")}}{{jsxref("Object.isExtensible()")}}
+ {{jsxref("Reflect.isExtensible()")}}
Object.isExtensible(proxy) должно возвращать тоже значение, что и Object.isExtensible(target).
{{jsxref("Global_Objects/Proxy/handler/preventExtensions", "handler.preventExtensions()")}}{{jsxref("Object.preventExtensions()")}}
+ {{jsxref("Reflect.preventExtensions()")}}
Object.preventExtensions(proxy) возвращает  true только в том случае, если Object.isExtensible(proxy) равно false.
{{jsxref("Global_Objects/Proxy/handler/getOwnPropertyDescriptor", "handler.getOwnPropertyDescriptor()")}}{{jsxref("Object.getOwnPropertyDescriptor()")}}
+ {{jsxref("Reflect.getOwnPropertyDescriptor()")}}
+
    +
  • метод getOwnPropertyDescriptor должен возвращать object или undefined.
  • +
  • Свойство не может быть описано как несуществующее, если оно существует и является некофигурируемым, собственным свойством целевого объекта target.
  • +
  • Свойство не может быть описано как несуществующее, если оно существует как собственное свойство целевого объекта target и target не расширяем.
  • +
  • Свойство не может быть описано как существующее, если оно не существует как собственное свойство  целевого объекта target и target не расширяем.
  • +
  • Свойство не может быть описано как неизменяемое, если оно не существует как собственное свойство целевого объекта target или если оно существует и является изменяемым, собственным свойством целевого объекта target.
  • +
  • Значение возвращенное методом Object.getOwnPropertyDescriptor(target) может быть применено к целевому объекту через метод Object.defineProperty и это не вызовет ошибки.
  • +
+
{{jsxref("Global_Objects/Proxy/handler/defineProperty", "handler.defineProperty()")}}{{jsxref("Object.defineProperty()")}}
+ {{jsxref("Reflect.defineProperty()")}}
+
    +
  • Новое свойство не может быть добавлено, если целевой объект не расширяем.
  • +
  • Нельзя добавить новое конфигурируемое свойство, или преобразовать существующее свойство в конфигурируемое, если оно не существует как собственное свойство целевого объекта или не является конфигурируемым.
  • +
  • Свойство не может быть неконфигурируемым, если целевой объект имеет соответствующее собственное, конфигурируемое свойство.
  • +
  • Если объект имеет свойство соответствующее создаваемому свойству, то Object.defineProperty(target, prop, descriptor) не вызовет ошибки.
  • +
  • В строгом режиме ("use strict";), если обработчик defineProperty вернет false, это вызовет ошибку {{jsxref("TypeError")}}.
  • +
+
{{jsxref("Global_Objects/Proxy/handler/has", "handler.has()")}}Property query: foo in proxy
+ Inherited property query: foo in Object.create(proxy)
+ {{jsxref("Reflect.has()")}}
+
    +
  • Свойство не может быть описано как несуществующее, если оно существует как собственное неконфигурируемое свойство целевого объекта.
  • +
  • Свойство не может быть описано как несуществующее, если оно существует как собственное свойство целевого объекта, и целевой объект является нерасширяемым.
  • +
+
{{jsxref("Global_Objects/Proxy/handler/get", "handler.get()")}}Property access: proxy[foo]and proxy.bar
+ Inherited property access: Object.create(proxy)[foo]
+ {{jsxref("Reflect.get()")}}
+
    +
  • Значение, возвращаемое для свойства, должно равняться значению соответствующего свойства целевого объекта, если это свойство является доступным только для чтения, неконфигурируемым.
  • +
  • Значение, возвращаемое для свойства, должно равняться undefined, если соответствующее свойство целевого объекта является неконфигурируемым и обернуто в геттер и сеттер, где сеттер равен undefined.
  • +
+
{{jsxref("Global_Objects/Proxy/handler/set", "handler.set()")}}Property assignment: proxy[foo] = bar and proxy.foo = bar
+ Inherited property assignment: Object.create(proxy)[foo] = bar
+ {{jsxref("Reflect.set()")}}
+
    +
  • Нельзя изменить значение свойства на значение, отличное от значения соответствующего свойства целевого объекта, если это свойство целевого объекта доступно только для чтения, и является неконфигурируемым.
  • +
  • Нельзя установить значение свойства, если соответствующее свойство целевого объекта является неконфигурируемым, и обернуто в геттер и сеттер, где сеттер равен undefined.
  • +
  • В строгом режиме, возвращение false из обработчика set вызовет ошибку {{jsxref("TypeError")}}.
  • +
+
{{jsxref("Global_Objects/Proxy/handler/deleteProperty", "handler.deleteProperty()")}}Property deletion: delete proxy[foo] and delete proxy.foo
+ {{jsxref("Reflect.deleteProperty()")}}
Свойство не может быть удалено, если оно существует в целевом объекте как собственное, неконфигурируемое свойство.
{{jsxref("Global_Objects/Proxy/handler/enumerate", "handler.enumerate()")}}Property enumeration / for...in: for (var name in proxy) {...}
+ {{jsxref("Reflect.enumerate()")}}
Метод enumerate должен возвращать объект.
{{jsxref("Global_Objects/Proxy/handler/ownKeys", "handler.ownKeys()")}}{{jsxref("Object.getOwnPropertyNames()")}}
+ {{jsxref("Object.getOwnPropertySymbols()")}}
+ {{jsxref("Object.keys()")}}
+ {{jsxref("Reflect.ownKeys()")}}
+
    +
  • Метод ownKeys должен возвращать список.
  • +
  • Типом каждого элемента в возвращаемом списке должен быть {{jsxref("String")}} или {{jsxref("Symbol")}}.
  • +
  • Возвращаемый список должен содержать ключи для всех неконфигурируемых, собственных свойств целевого объекта.
  • +
  • Если целевой объект является нерасширяемым, возвращаемый список должен содержать все ключи для собственных полей целевого объекта и больше никаких других значений.
  • +
+
{{jsxref("Global_Objects/Proxy/handler/apply", "handler.apply()")}}proxy(..args)
+ {{jsxref("Function.prototype.apply()")}} and {{jsxref("Function.prototype.call()")}}
+ {{jsxref("Reflect.apply()")}}
Ограничений нет.
{{jsxref("Global_Objects/Proxy/handler/construct", "handler.construct()")}}new proxy(...args)
+ {{jsxref("Reflect.construct()")}}
Обработчик должен возвращать Object.
+ +

Отзываемый Proxy

+ +

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

+ +
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 нет ловушек
+ +

Рефлексия

+ +

{{jsxref("Reflect")}} это встроенный объект, предоставляющий методы для перехватываемых операций JavaScript. Это те же самые методы, что имеются в {{jsxref("Global_Objects/Proxy/handler","обработчиках Proxy","","true")}}. Объект Reflect не является функцией.

+ +

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

+ +

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

+ +
Reflect.has(Object, 'assign'); // true
+
+ +

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

+ +

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

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

С методом {{jsxref("Reflect.apply")}} эта операция менее громоздка и более понятна:

+ +
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"
+
+ +

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

+ +

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

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

{{Previous("Web/JavaScript/Guide/Iterators_and_Generators")}}

-- cgit v1.2.3-54-g00ecf