From 074785cea106179cb3305637055ab0a009ca74f2 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:42:52 -0500 Subject: initial commit --- .../reference/global_objects/proxy/index.html | 482 +++++++++++++++++++++ 1 file changed, 482 insertions(+) create mode 100644 files/ru/web/javascript/reference/global_objects/proxy/index.html (limited to 'files/ru/web/javascript/reference/global_objects/proxy/index.html') diff --git a/files/ru/web/javascript/reference/global_objects/proxy/index.html b/files/ru/web/javascript/reference/global_objects/proxy/index.html new file mode 100644 index 0000000000..2d8e5ae557 --- /dev/null +++ b/files/ru/web/javascript/reference/global_objects/proxy/index.html @@ -0,0 +1,482 @@ +--- +title: Прокси +slug: Web/JavaScript/Reference/Global_Objects/Proxy +tags: + - ECMAScript6 + - JavaScript + - NeedsUpdate + - Reference + - Объект + - Прокси +translation_of: Web/JavaScript/Reference/Global_Objects/Proxy +--- +
Объект Proxy позволяет создать прокси для другого объекта, может перехватывать и переопределить основные операции для данного объекта.
+ +

Введение

+ +

Прокси используются программистами для объявления расширенной семантики JavaScript объектов. Стандартная семантика реализована в движке JavaScript, который обычно написан на низкоуровневом языке программирования, например C++. Прокси позволяют программисту определить поведение объекта при помощи JavaScript. Другими словами они являются инструментом метапрограммирования.

+ +

Примечание: реализация прокси в SpiderMonkey является прототипом, в котором прокси API и семантика не стабильны. Также, реализация в SpiderMonkey может не соответствовать последней версии спецификации. Она может быть изменена в любой момент и предоставляется исключительно как экспериментальная функция. Не полагайтесь на неё в производственном коде.

+ +

Эта страница описывает новый API (называемый «непосредственным проксированием»), который является частью Firefox 18. Для просмотра старого API (Firefox 17 и ниже) посетите страницу описания старого прокси API.

+ +

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

+ +
+
механизм полного перехвата (или "intercession API")
+
Технический термин для этой функции.
+
прокси (proxy)
+
Объект, оборачивающий исходный объект.
+
обработчик (handler)
+
Объект-заменитель, содержащий ловушки. Определяет, какие операции будут перехвачены, также переопределяет перехваченные операции.
+
ловушки (traps)
+
Методы, которые предоставляют доступ к свойствам. Это аналогично концепции ловушек в операционных системах.
+
цель (target)
+
Исходный объект, который виртуализируется прокси. Он часто используется в качестве источника данных в прокси. Для него проверяются инварианты относительно расширяемости и настраиваемости свойств.
+
+ +

Прокси

+ +

Прокси - это новые объекты; невозможно выполнить "проксирование" существующего объекта. Пример создания прокси:

+ +
var p = new Proxy(target, handler);
+
+ +

Где:

+ + + +

Обработчик

+ +

Все ловушки опциональны. В случае, если ловушка не задана, то стандартным поведением будет перенаправление операции к объекту-цели.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
JavaScript кодМетод обработчикаОписание
Object.getOwnPropertyDescriptor(proxy, name)getOwnPropertyDescriptor
+ function(target, name) -> PropertyDescriptor | undefined
Должен возвращать верный объект-описание свойства или undefined, чтобы показать, что свойство с именем name существует в эмулируемом объекте.
+

Object.getOwnPropertyNames(proxy)
+ Object.getOwnPropertySymbols(proxy)
+ Object.keys(proxy)

+
ownKeys function(target) -> [string | symbol]Возвращает массив всех собственных (не унаследованных) имён свойств эмулируемого объекта.
Object.defineProperty(proxy,name,pd)defineProperty function(target, name, propertyDescriptor) -> anyЗадаёт новое свойство, атрибуты которого определяются предоставленным propertyDescriptor. Возвращаемое значение метода игнорируется.
delete proxy.namedeleteProperty function(target, name) -> booleanУдаляет именованное свойство из прокси. Возвращает true в случае успешного удаления свойства name.
Object.preventExtensions(proxy)preventExtensions function(target) -> booleanДелает объект нерасширяемым. Возвращает true при успешном выполнении.
name in proxyhas function(target, name) -> boolean
+

proxy.name (in the context of "getting the value")

+ +

receiver.name (if receiver inherits from a proxy and does not override name)

+
get function(target, name, receiver) -> anyreceiver — это прокси или объект, унаследованный от прокси.
+

proxy.name = val (in the context of "setting the value")

+ +

receiver.name = val (if receiver inherits from a proxy and does not override name)

+
set function(target, name, val, receiver) -> booleanreceiver — это прокси или объект, унаследованный от прокси.
+

proxy(...args)
+ proxy.apply(thisValue, args)
+ proxy.call(thisValue, ...args)

+
apply function(target, thisValue, args) -> anytarget должен быть функцией.
new proxy(...args)construct function(target, args) -> objecttarget должен быть функцией.
+ +

Инварианты

+ +

Несмотря на то, что прокси предоставляют много возможностей пользователям, некоторые операции не перехватываются для сохранения постоянства языка:

+ + + +

Примеры

+ +

Простой пример

+ +

Объект, возвращающий значение 37, в случае отсутствия свойства с указанным именем:

+ +
var handler = {
+    get: function(target, name){
+        return name in target?
+            target[name] :
+            37;
+    }
+};
+
+var p = new Proxy({}, handler);
+p.a = 1;
+p.b = undefined;
+
+console.log(p.a, p.b); // 1, undefined
+console.log('c' in p, p.c); // false, 37
+
+ +

Перенаправляющий прокси

+ +

В данном примере мы используем JavaScript объект, к которому наш прокси направляет все запросы:

+ +
var target = {};
+var p = new Proxy(target, {});
+
+p.a = 37; // операция перенаправлена прокси
+
+console.log(target.a); // 37. Операция была успешно перенаправлена
+
+ +

Проверка

+ +

При помощи Proxy вы можете легко проверять передаваемые объекту значения:

+ +
let validator = {
+  set: function(obj, prop, value) {
+    if (prop === 'age') {
+      if (!Number.isInteger(value)) {
+        throw new TypeError('The age is not an integer');
+      }
+      if (value > 200) {
+        throw new RangeError('The age seems invalid');
+      }
+    }
+
+    // Стандартное сохранение значения
+    obj[prop] = value;
+  }
+};
+
+let person = new Proxy({}, validator);
+
+person.age = 100;
+console.log(person.age); // 100
+person.age = 'young'; // Вызовет исключение
+person.age = 300; // Вызовет исключение
+
+ +

Дополнение конструктора

+ +

Функция прокси может легко дополнить конструктор новым:

+ +
function extend(sup, base) {
+    var descriptor = Object.getOwnPropertyDescriptor(
+        base.prototype, 'constructor',
+    );
+
+    const prototype = {...base.prototype}
+
+    base.prototype = Object.create(sup.prototype);
+    base.prototype = Object.assign(base.prototype, prototype);
+
+    var handler = {
+        construct: function(target, args) {
+            var obj = Object.create(base.prototype);
+            this.apply(target, obj, args);
+            return obj;
+        },
+        apply: function(target, that, args) {
+            sup.apply(that, args);
+            base.apply(that, args);
+        },
+    };
+    var proxy = new Proxy(base, handler);
+    descriptor.value = proxy;
+    Object.defineProperty(base.prototype, 'constructor', descriptor);
+    return proxy;
+}
+
+var Person = function(name) {
+    this.name = name;
+};
+
+var Boy = extend(Person, function(name, age) {
+    this.age = age;
+});
+
+Boy.prototype.sex = 'M';
+
+var Peter = new Boy('Peter', 13);
+console.log(Peter.sex); // "M"
+console.log(Peter.name); // "Peter"
+console.log(Peter.age); // 13
+
+ +

Манипуляция DOM элементами

+ +

Иногда возникает необходимость переключить атрибут или имя класса у двух разных элементов:

+ +
let view = new Proxy({
+  selected: null
+},
+{
+  set: function(obj, prop, newval) {
+    let oldval = obj[prop];
+
+    if (prop === 'selected') {
+      if (oldval) {
+        oldval.setAttribute('aria-selected', 'false');
+      }
+      if (newval) {
+        newval.setAttribute('aria-selected', 'true');
+      }
+    }
+
+    // Стандартное сохранение значения
+    obj[prop] = newval;
+  }
+});
+
+let i1 = view.selected = document.getElementById('item-1');
+console.log(i1.getAttribute('aria-selected')); // 'true'
+
+let i2 = view.selected = document.getElementById('item-2');
+console.log(i1.getAttribute('aria-selected')); // 'false'
+console.log(i2.getAttribute('aria-selected')); // 'true'
+
+ +

Изменение значений и дополнительные свойства

+ +

Прокси объект products проверяет передаваемые значения и преобразует их в массив в случае необходимости. Объект также поддерживает дополнительное свойство latestBrowser на чтение и запись.

+ +
let products = new Proxy({
+  browsers: ['Internet Explorer', 'Netscape']
+},
+{
+  get: function(obj, prop) {
+    // Дополнительное свойство
+    if (prop === 'latestBrowser') {
+      return obj.browsers[obj.browsers.length - 1];
+    }
+
+    // Стандартный возврат значения
+    return obj[prop];
+  },
+  set: function(obj, prop, value) {
+    // Дополнительное свойство
+    if (prop === 'latestBrowser') {
+      obj.browsers.push(value);
+      return;
+    }
+
+    // Преобразование значения, если оно не массив
+    if (typeof value === 'string') {
+      value = [value];
+    }
+
+    // Стандартное сохранение значения
+    obj[prop] = value;
+  }
+});
+
+console.log(products.browsers); // ['Internet Explorer', 'Netscape']
+products.browsers = 'Firefox'; // передаётся как строка (по ошибке)
+console.log(products.browsers); // ['Firefox'] <- проблем нет, значение - массив
+
+products.latestBrowser = 'Chrome';
+console.log(products.browsers); // ['Firefox', 'Chrome']
+console.log(products.latestBrowser); // 'Chrome'
+
+ +

Поиск элемента массива по его свойству

+ +

Данный прокси расширяет массив дополнительными возможностями. Как вы видите, вы можете гибко "задавать" свойства без использования Object.defineProperties. Данный пример также может быть использован для поиска строки таблицы по её ячейке. В этом случае целью будет table.rows.

+ +
let products = new Proxy([
+  { name: 'Firefox', type: 'browser' },
+  { name: 'SeaMonkey', type: 'browser' },
+  { name: 'Thunderbird', type: 'mailer' }
+],
+{
+  get: function(obj, prop) {
+    // Стандартное возвращение значения; prop обычно является числом
+    if (prop in obj) {
+      return obj[prop];
+    }
+
+    // Получение количества продуктов; псевдоним products.length
+    if (prop === 'number') {
+      return obj.length;
+    }
+
+    let result, types = {};
+
+    for (let product of obj) {
+      if (product.name === prop) {
+        result = product;
+      }
+      if (types[product.type]) {
+        types[product.type].push(product);
+      } else {
+        types[product.type] = [product];
+      }
+    }
+
+    // Получение продукта по имени
+    if (result) {
+      return result;
+    }
+
+    // Получение продуктов по типу
+    if (prop in types) {
+      return types[prop];
+    }
+
+    // Получение типов продуктов
+    if (prop === 'types') {
+      return Object.keys(types);
+    }
+
+    return undefined;
+  }
+});
+
+console.log(products[0]); // { name: 'Firefox', type: 'browser' }
+console.log(products['Firefox']); // { name: 'Firefox', type: 'browser' }
+console.log(products['Chrome']); // undefined
+console.log(products.browser); // [{ name: 'Firefox', type: 'browser' }, { name: 'SeaMonkey', type: 'browser' }]
+console.log(products.types); // ['browser', 'mailer']
+console.log(products.number); // 3
+
+ +

Пример использования всех перехватчиков

+ +

В данном примере, использующем все виды перехватчиков, мы попытаемся проксировать не нативный объект, который частично приспособлен для этого - docCookies, созданном в разделе "little framework" и опубликованном на странице document.cookie.

+ +
/*
+  var docCookies = ... получить объект "docCookies" можно здесь:
+  https://developer.mozilla.org/en-US/docs/DOM/document.cookie#A_little_framework.3A_a_complete_cookies_reader.2Fwriter_with_full_unicode_support
+*/
+
+var docCookies = new Proxy(docCookies, {
+  "get": function (oTarget, sKey) {
+    return oTarget[sKey] || oTarget.getItem(sKey) || undefined;
+  },
+  "set": function (oTarget, sKey, vValue) {
+    if (sKey in oTarget) { return false; }
+    return oTarget.setItem(sKey, vValue);
+  },
+  "deleteProperty": function (oTarget, sKey) {
+    if (sKey in oTarget) { return false; }
+    return oTarget.removeItem(sKey);
+  },
+  "enumerate": function (oTarget, sKey) {
+    return oTarget.keys();
+  },
+  "iterate": function (oTarget, sKey) {
+    return oTarget.keys();
+  },
+  "ownKeys": function (oTarget, sKey) {
+    return oTarget.keys();
+  },
+  "has": function (oTarget, sKey) {
+    return sKey in oTarget || oTarget.hasItem(sKey);
+  },
+  "hasOwn": function (oTarget, sKey) {
+    return oTarget.hasItem(sKey);
+  },
+  "defineProperty": function (oTarget, sKey, oDesc) {
+    if (oDesc && "value" in oDesc) { oTarget.setItem(sKey, oDesc.value); }
+    return oTarget;
+  },
+  "getPropertyNames": function (oTarget) {
+    return Object.getPropertyNames(oTarget).concat(oTarget.keys());
+  },
+  "getOwnPropertyNames": function (oTarget) {
+    return Object.getOwnPropertyNames(oTarget).concat(oTarget.keys());
+  },
+  "getPropertyDescriptor": function (oTarget, sKey) {
+    var vValue = oTarget[sKey] || oTarget.getItem(sKey)
+    return vValue ? {
+      "value": vValue,
+      "writable": true,
+      "enumerable": true,
+      "configurable": false
+    } : undefined;
+  },
+  "getOwnPropertyDescriptor": function (oTarget, sKey) {
+    var vValue = oTarget.getItem(sKey);
+    return vValue ? {
+      "value": vValue,
+      "writable": true,
+      "enumerable": true,
+      "configurable": false
+    } : undefined;
+  },
+  "fix":  function (oTarget) {
+    return "not implemented yet!";
+  },
+});
+
+/* Проверка cookies */
+
+alert(docCookies.my_cookie1 = "First value");
+alert(docCookies.getItem("my_cookie1"));
+
+docCookies.setItem("my_cookie1", "Changed value");
+alert(docCookies.my_cookie1);
+ +

Смотрите также

+ + + +

Лицензионные примечания

+ +

Некоторое содержимое (текст, примеры) данной страницы было скопировано или адаптировано со страниц вики ECMAScript, имеющей лицензию CC 2.0 BY-NC-SA

-- cgit v1.2.3-54-g00ecf