diff options
author | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:42:52 -0500 |
---|---|---|
committer | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:42:52 -0500 |
commit | 074785cea106179cb3305637055ab0a009ca74f2 (patch) | |
tree | e6ae371cccd642aa2b67f39752a2cdf1fd4eb040 /files/ru/web/javascript/reference/global_objects/proxy/index.html | |
parent | da78a9e329e272dedb2400b79a3bdeebff387d47 (diff) | |
download | translated-content-074785cea106179cb3305637055ab0a009ca74f2.tar.gz translated-content-074785cea106179cb3305637055ab0a009ca74f2.tar.bz2 translated-content-074785cea106179cb3305637055ab0a009ca74f2.zip |
initial commit
Diffstat (limited to 'files/ru/web/javascript/reference/global_objects/proxy/index.html')
-rw-r--r-- | files/ru/web/javascript/reference/global_objects/proxy/index.html | 482 |
1 files changed, 482 insertions, 0 deletions
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 +--- +<div><font><font>Объект </font></font><code>Proxy</code><font><font> позволяет создать прокси</font><font> для другого объекта, может перехватывать и переопределить основные операции для данного объекта.</font></font></div> + +<h2 id="Введение">Введение</h2> + +<p>Прокси используются программистами для объявления расширенной семантики JavaScript объектов. Стандартная семантика реализована в движке JavaScript, который обычно написан на низкоуровневом языке программирования, например C++. Прокси позволяют программисту определить поведение объекта при помощи JavaScript. Другими словами они являются <strong>инструментом метапрограммирования</strong>.</p> + +<p><strong>Примечание</strong>: реализация прокси в SpiderMonkey является прототипом, в котором прокси API и семантика не стабильны. Также, реализация в SpiderMonkey может не соответствовать последней версии спецификации. Она может быть изменена в любой момент и предоставляется исключительно как экспериментальная функция. <strong>Не полагайтесь на неё в производственном коде.</strong></p> + +<p>Эта страница описывает новый API (называемый «непосредственным проксированием»), который является частью Firefox 18. Для просмотра старого API (Firefox 17 и ниже) посетите страницу описания <a href="/en-US/docs/JavaScript/Old_Proxy_API" title="/en-US/docs/JavaScript/Old_Proxy_API">старого прокси API</a>.</p> + +<h2 id="Терминология">Терминология</h2> + +<dl> + <dt>механизм полного перехвата (или "intercession API")</dt> + <dd>Технический термин для этой функции.</dd> + <dt>прокси (proxy)</dt> + <dd>Объект, оборачивающий исходный объект.</dd> + <dt>обработчик (handler)</dt> + <dd>Объект-заменитель, содержащий ловушки. О<span class="VIiyi" lang="ru"><span class="ChMk0b JLqJ4b"><span>пределяет, какие операции будут перехвачены, также переопределяет перехваченные операции.</span></span></span></dd> + <dt>ловушки (traps)</dt> + <dd>Методы, которые предоставляют доступ к свойствам. Это аналогично концепции ловушек в операционных системах.</dd> + <dt>цель (target)</dt> + <dd>Исходный объект, который виртуализируется прокси. Он часто используется в качестве источника данных в прокси. Для него проверяются инварианты относительно расширяемости и настраиваемости свойств.</dd> +</dl> + +<h2 id="Прокси">Прокси</h2> + +<p>Прокси - это новые объекты; невозможно выполнить "проксирование" существующего объекта. Пример создания прокси:</p> + +<pre class="brush: js notranslate">var p = new Proxy(target, handler); +</pre> + +<p>Где:</p> + +<ul> + <li><code>target</code> — исходный объект (может быть объектом любого типа, включая массив, функцию и даже другой прокси объект).</li> + <li><code>handler</code> — объект-обработчик, методы (ловушки) которого определяют поведение прокси во время выполнения операции над ним.</li> +</ul> + +<h2 id="Обработчик">Обработчик</h2> + +<p>Все ловушки опциональны. В случае, если ловушка не задана, то стандартным поведением будет перенаправление операции к объекту-цели.</p> + +<table class="standard-table"> + <thead> + <tr> + <th>JavaScript код</th> + <th>Метод обработчика</th> + <th>Описание</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>Object.getOwnPropertyDescriptor(proxy, name)</code></td> + <td><code><strong>getOwnPropertyDescriptor</strong><br> + function(target, name) -> PropertyDescriptor | undefined</code></td> + <td>Должен возвращать верный объект-описание свойства или <code>undefined</code>, чтобы показать, что свойство с именем <code>name</code> существует в эмулируемом объекте.</td> + </tr> + <tr> + <td> + <p><code>Object.getOwnPropertyNames(proxy)</code><br> + <code><code>Object.getOwnPropertySymbols(proxy)</code></code><br> + <code><code><code>Object.keys(proxy)</code></code></code></p> + </td> + <td><code><code><strong>ownKeys</strong></code> function(target) -> [string | symbol]</code></td> + <td>Возвращает массив всех собственных (не унаследованных) имён свойств эмулируемого объекта.</td> + </tr> + <tr> + <td><code>Object.defineProperty(proxy,name,pd)</code></td> + <td><code><strong>defineProperty</strong> function(target, name, propertyDescriptor) -> any</code></td> + <td>Задаёт новое свойство, атрибуты которого определяются предоставленным <code>propertyDescriptor</code>. Возвращаемое значение метода игнорируется.</td> + </tr> + <tr> + <td><code>delete proxy.name</code></td> + <td><code><strong>deleteProperty</strong> function(target, name) -> boolean</code></td> + <td>Удаляет именованное свойство из прокси. Возвращает <code>true</code> в случае успешного удаления свойства <code>name</code>.</td> + </tr> + <tr> + <td><code>Object.preventExtensions(proxy)</code></td> + <td><code><strong>preventExtensions</strong> function(target) -> boolean</code></td> + <td>Делает объект нерасширяемым. Возвращает <code>true</code> при успешном выполнении.</td> + </tr> + <tr> + <td><code>name in proxy</code></td> + <td><code><strong>has</strong> function(target, name) -> boolean</code></td> + <td></td> + </tr> + <tr> + <td> + <p><code>proxy.name</code> (in the context of "getting the value")</p> + + <p><code>receiver.name</code> (if <code>receiver</code> inherits from a proxy and does not override <code>name</code>)</p> + </td> + <td><code><strong>get</strong> function(target, name, receiver) -> any</code></td> + <td><code>receiver</code> — это прокси или объект, унаследованный от прокси.</td> + </tr> + <tr> + <td> + <p><code>proxy.name = val</code> (in the context of "setting the value")</p> + + <p><code>receiver.name = val</code> (if <code>receiver</code> inherits from a proxy and does not override <code>name</code>)</p> + </td> + <td><code><strong>set</strong> function(target, name, val, receiver) -> boolean</code></td> + <td><code style="font-style: normal;">receiver</code> — это прокси или объект, унаследованный от прокси.</td> + </tr> + <tr> + <td> + <p><code>proxy(...args)<br> + proxy.apply(thisValue, args)<br> + proxy.call(thisValue, ...args)</code></p> + </td> + <td><code><strong>apply</strong> function(target, thisValue, args) -> any</code></td> + <td><code>target</code> должен быть функцией.</td> + </tr> + <tr> + <td><code>new proxy(...args)</code></td> + <td><code><strong>construct</strong> function(target, args) -> object</code></td> + <td><code>target</code> должен быть функцией.</td> + </tr> + </tbody> +</table> + +<h2 id="Инварианты">Инварианты</h2> + +<p>Несмотря на то, что прокси предоставляют много возможностей пользователям, некоторые операции не перехватываются для сохранения постоянства языка:</p> + +<ul> + <li>Простой и строгий оператор равенства (<code>==</code>, <code>===</code>) не перехватывается. <code>p1 === p2</code> равны, только если <code>p1</code> и <code>p2</code> ссылаются на один и тот же прокси.</li> + <li>Текущая реализация <code>Object.getPrototypeOf(proxy)</code> всегда возвращает <code>Object.getPrototypeOf(target)</code>, потому что в ES2015 перехватчик getPrototypeOf пока не реализован.</li> + <li><code>typeof proxy</code> всегда возвращает <code>typeof target</code>. В частности, <code>proxy</code> может быть использован как функция только если <code>target </code>является функцией.</li> + <li><code>Array.isArray(proxy)</code> всегда возвращает <code>Array.isArray(target)</code>.</li> + <li><code>Object.prototype.toString.call(proxy)</code> всегда возвращает <code>Object.prototype.toString.call(target)</code>, потому что в ES2015 перехватчик Symbol.toStringTag пока не реализован.</li> +</ul> + +<h2 id="Примеры">Примеры</h2> + +<h3 id="Простой_пример">Простой пример</h3> + +<p>Объект, возвращающий значение <code>37</code>, в случае отсутствия свойства с указанным именем:</p> + +<pre class="brush: js notranslate">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 +</pre> + +<h3 id="Перенаправляющий_прокси">Перенаправляющий прокси</h3> + +<p>В данном примере мы используем JavaScript объект, к которому наш прокси направляет все запросы:</p> + +<pre class="brush: js notranslate">var target = {}; +var p = new Proxy(target, {}); + +p.a = 37; // операция перенаправлена прокси + +console.log(target.a); // 37. Операция была успешно перенаправлена +</pre> + +<h3 id="Проверка">Проверка</h3> + +<p>При помощи <code>Proxy</code> вы можете легко проверять передаваемые объекту значения:</p> + +<pre class="brush: js notranslate">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; // Вызовет исключение +</pre> + +<h3 id="Дополнение_конструктора">Дополнение конструктора</h3> + +<p>Функция прокси может легко дополнить конструктор новым:</p> + +<pre class="brush: js notranslate">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 +</pre> + +<h3 id="Манипуляция_DOM_элементами">Манипуляция<span style="font-size: 1.71428571428571rem;"> DOM </span><span style="font-size: 1.71428571428571rem;">элементами</span></h3> + +<p>Иногда возникает необходимость переключить атрибут или имя класса у двух разных элементов:</p> + +<pre class="brush: js notranslate">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' +</pre> + +<h3 id="Изменение_значений_и_дополнительные_свойства">Изменение значений и дополнительные свойства</h3> + +<p>Прокси объект <code>products</code> проверяет передаваемые значения и преобразует их в массив в случае необходимости. Объект также поддерживает дополнительное свойство <code>latestBrowser</code> на чтение и запись.</p> + +<pre class="brush: js notranslate">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' +</pre> + +<h3 id="Поиск_элемента_массива_по_его_свойству">Поиск элемента массива по его свойству</h3> + +<p>Данный прокси расширяет массив дополнительными возможностями. Как вы видите, вы можете гибко "задавать" свойства без использования <a href="/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperties"><code>Object.defineProperties</code></a>. Данный пример также может быть использован для поиска строки таблицы по её ячейке. В этом случае целью будет <a href="/en-US/docs/DOM/table.rows"><code>table.rows</code></a>.</p> + +<pre class="brush: js notranslate">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 +</pre> + +<h3 id="Пример_использования_всех_перехватчиков">Пример использования всех перехватчиков</h3> + +<p>В данном примере, использующем все виды перехватчиков, мы попытаемся проксировать <em>не нативный</em> объект, который частично приспособлен для этого - <code>docCookies,</code> созданном в разделе <a href="https://developer.mozilla.org/en-US/docs/DOM/document.cookie#A_little_framework.3A_a_complete_cookies_reader.2Fwriter_with_full_unicode_support" title="https://developer.mozilla.org/en-US/docs/DOM/document.cookie#A_little_framework.3A_a_complete_cookies_reader.2Fwriter_with_full_unicode_support">"little framework" и опубликованном на странице <code>document.cookie</code></a>.</p> + +<pre class="brush: js notranslate">/* + 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);</pre> + +<h2 id="See_also" name="See_also" style="line-height: 30px; font-size: 2.14285714285714rem;">Смотрите также</h2> + +<ul> + <li><a class="external" href="http://jsconf.eu/2010/speaker/be_proxy_objects.html">"Proxies are awesome" презентация Brendan Eich на JSConf</a> (<a class="external" href="http://www.slideshare.net/BrendanEich/metaprog-5303821">слайды</a>)</li> + <li><a class="external" href="http://wiki.ecmascript.org/doku.php?id=harmony:proxies">Страница предложения ECMAScript Harmony Proxy</a> и <a class="external" href="http://wiki.ecmascript.org/doku.php?id=harmony:proxies_semantics">страница ECMAScript Harmony proxy semantics</a></li> + <li><a class="external" href="http://soft.vub.ac.be/~tvcutsem/proxies/">Руководство по прокси</a></li> + <li><a href="/en-US/docs/JavaScript/Old_Proxy_API" title="/en-US/docs/JavaScript/Old_Proxy_API">Старая страница Proxy API</a></li> + <li><a href="/en-US/docs/JavaScript/Reference/Global_Objects/Object/watch"><code>Object.watch</code></a> - не стандартная возможность, поддерживается только в движке Gecko.</li> +</ul> + +<h2 id="Лицензионные_примечания">Лицензионные примечания</h2> + +<p>Некоторое содержимое (текст, примеры) данной страницы было скопировано или адаптировано со страниц <a class="external" href="http://wiki.ecmascript.org/doku.php">вики ECMAScript</a>, имеющей лицензию <a class="external" href="http://creativecommons.org/licenses/by-nc-sa/2.0/">CC 2.0 BY-NC-SA</a></p> |