diff options
| author | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:40:17 -0500 |
|---|---|---|
| committer | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:40:17 -0500 |
| commit | 33058f2b292b3a581333bdfb21b8f671898c5060 (patch) | |
| tree | 51c3e392513ec574331b2d3f85c394445ea803c6 /files/zh-cn/web/javascript/reference/global_objects/proxy/index.html | |
| parent | 8b66d724f7caf0157093fb09cfec8fbd0c6ad50a (diff) | |
| download | translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.gz translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.bz2 translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.zip | |
initial commit
Diffstat (limited to 'files/zh-cn/web/javascript/reference/global_objects/proxy/index.html')
| -rw-r--r-- | files/zh-cn/web/javascript/reference/global_objects/proxy/index.html | 435 |
1 files changed, 435 insertions, 0 deletions
diff --git a/files/zh-cn/web/javascript/reference/global_objects/proxy/index.html b/files/zh-cn/web/javascript/reference/global_objects/proxy/index.html new file mode 100644 index 0000000000..f9059ade82 --- /dev/null +++ b/files/zh-cn/web/javascript/reference/global_objects/proxy/index.html @@ -0,0 +1,435 @@ +--- +title: Proxy +slug: Web/JavaScript/Reference/Global_Objects/Proxy +tags: + - ECMAScript 2015 + - JavaScript + - Proxy +translation_of: Web/JavaScript/Reference/Global_Objects/Proxy +--- +<div>{{JSRef}}</div> + +<p><strong>Proxy</strong> 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。</p> + +<h2 id="术语">术语</h2> + +<dl> + <dt>{{jsxref("Global_Objects/Proxy/handler", "<dfn>handler</dfn>")}}</dt> + <dd>包含捕捉器(trap)的占位符对象,可译为处理器对象。</dd> + <dt><dfn>traps</dfn></dt> + <dd>提供属性访问的方法。这类似于操作系统中捕获器的概念。</dd> + <dt><dfn>target</dfn></dt> + <dd>被 Proxy 代理虚拟化的对象。它常被作为代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)。</dd> +</dl> + +<h2 id="语法">语法</h2> + +<pre class="syntaxbox notranslate">const <var>p</var> = new Proxy(<var>target</var>, <var>handler</var>)</pre> + +<h3 id="参数">参数</h3> + +<dl> + <dt><code><var>target</var></code></dt> + <dd>要使用 <code>Proxy</code> 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。</dd> + <dt><code><var>handler</var></code></dt> + <dd>一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 <code><var>p</var></code> 的行为。</dd> +</dl> + +<h2 id="方法">方法</h2> + +<dl> + <dt>{{jsxref("Proxy.revocable()")}}</dt> + <dd>创建一个可撤销的<code>Proxy</code>对象。</dd> +</dl> + +<h2 id="handler_对象的方法">handler 对象的方法</h2> + +<p><code><var>handler</var></code> 对象是一个容纳一批特定属性的占位符对象。它包含有 <code>Proxy</code> 的各个捕获器(trap)。</p> + +<p>所有的捕捉器是可选的。如果没有定义某个捕捉器,那么就会保留源对象的默认行为。</p> + +<dl> + <dt>{{JSxRef("Global_Objects/Proxy/handler/getPrototypeOf", "handler.getPrototypeOf()")}}</dt> + <dd>{{JSxRef("Object.getPrototypeOf")}} 方法的捕捉器。</dd> + <dt>{{JSxRef("Global_Objects/Proxy/handler/setPrototypeOf", "handler.setPrototypeOf()")}}</dt> + <dd>{{JSxRef("Object.setPrototypeOf")}} 方法的捕捉器。</dd> + <dt>{{JSxRef("Global_Objects/Proxy/handler/isExtensible", "handler.isExtensible()")}}</dt> + <dd>{{JSxRef("Object.isExtensible")}} 方法的捕捉器。</dd> + <dt>{{JSxRef("Global_Objects/Proxy/handler/preventExtensions", "handler.preventExtensions()")}}</dt> + <dd>{{JSxRef("Object.preventExtensions")}} 方法的捕捉器。</dd> + <dt>{{JSxRef("Global_Objects/Proxy/handler/getOwnPropertyDescriptor", "handler.getOwnPropertyDescriptor()")}}</dt> + <dd>{{JSxRef("Object.getOwnPropertyDescriptor")}} 方法的捕捉器。</dd> + <dt>{{JSxRef("Global_Objects/Proxy/handler/defineProperty", "handler.defineProperty()")}}</dt> + <dd>{{JSxRef("Object.defineProperty")}} 方法的捕捉器。</dd> + <dt>{{JSxRef("Global_Objects/Proxy/handler/has", "handler.has()")}}</dt> + <dd>{{JSxRef("Operators/in", "in")}} 操作符的捕捉器。</dd> + <dt>{{JSxRef("Global_Objects/Proxy/handler/get", "handler.get()")}}</dt> + <dd>属性读取操作的捕捉器。</dd> + <dt>{{JSxRef("Global_Objects/Proxy/handler/set", "handler.set()")}}</dt> + <dd>属性设置操作的捕捉器。</dd> + <dt>{{JSxRef("Global_Objects/Proxy/handler/deleteProperty", "handler.deleteProperty()")}}</dt> + <dd>{{JSxRef("Operators/delete", "delete")}} 操作符的捕捉器。</dd> + <dt>{{JSxRef("Global_Objects/Proxy/handler/ownKeys", "handler.ownKeys()")}}</dt> + <dd>{{JSxRef("Object.getOwnPropertyNames")}} 方法和 {{JSxRef("Object.getOwnPropertySymbols")}} 方法的捕捉器。</dd> + <dt>{{JSxRef("Global_Objects/Proxy/handler/apply", "handler.apply()")}}</dt> + <dd>函数调用操作的捕捉器。</dd> + <dt>{{JSxRef("Global_Objects/Proxy/handler/construct", "handler.construct()")}}</dt> + <dd>{{JSxRef("Operators/new", "new")}} 操作符的捕捉器。</dd> +</dl> + +<p>一些不标准的捕捉器已经被<a href="/zh-CN/docs/Web/JavaScript/Reference/Deprecated_and_obsolete_features#Proxy">废弃并且移除</a>了。</p> + +<h2 id="示例">示例</h2> + +<h3 id="基础示例">基础示例</h3> + +<p>在以下简单的例子中,当对象中不存在属性名时,默认返回值为 <code>37</code>。下面的代码以此展示了 {{jsxref("Global_Objects/Proxy/handler/get", "get")}} handler 的使用场景。</p> + +<pre class="brush: js notranslate">const handler = { + get: function(obj, prop) { + return prop in obj ? obj[prop] : 37; + } +}; + +const 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">let target = {}; +let p = new Proxy(target, {}); + +p.a = 37; // 操作转发到目标 + +console.log(target.a); // 37. 操作已经被正确地转发 +</pre> + +<h3 id="验证">验证</h3> + +<p>通过代理,你可以轻松地验证向一个对象的传值。下面的代码借此展示了 {{jsxref("Global_Objects/Proxy/handler/set", "set")}} handler 的作用。</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'); + } + } + + // The default behavior to store the value + obj[prop] = value; + + // 表示成功 + return true; + } +}; + +let person = new Proxy({}, validator); + +person.age = 100; + +console.log(person.age); +// 100 + +person.age = 'young'; +// 抛出异常: Uncaught TypeError: The age is not an integer + +person.age = 300; +// 抛出异常: Uncaught RangeError: The age seems invalid +</pre> + +<h3 id="扩展构造函数">扩展构造函数</h3> + +<p>方法代理可以轻松地通过一个新构造函数来扩展一个已有的构造函数。这个例子使用了<a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/construct"><code>construct</code></a>和<code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/apply">apply</a></code>。</p> + +<pre class="brush: js notranslate">function extend(sup, base) { + var descriptor = Object.getOwnPropertyDescriptor( + base.prototype, "constructor" + ); + base.prototype = Object.create(sup.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_节点">操作 DOM 节点</h3> + +<p>有时,我们可能需要互换两个不同的元素的属性或类名。下面的代码以此为目标,展示了 {{jsxref("Global_Objects/Proxy/handler/set", "set")}} handler 的使用场景。</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'); + } + } + + // 默认行为是存储被传入 setter 函数的属性值 + obj[prop] = newval; + + // 表示操作成功 + return true; + } +}); + +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>的附加属性,这个属性可以同时作为 getter 和 setter。</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; + + // 表示成功 + return true; + } +}); + +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>以下代理为数组扩展了一些实用工具。如你所见,通过 Proxy,我们可以灵活地“定义”属性,而不需要使用 {{jsxref("Object.defineProperties")}} 方法。以下例子可以用于通过单元格来查找表格中的一行。在这种情况下,target 是 <code><a href="/zh-CN/docs/DOM/table.rows">table.rows</a></code>。</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 的 number; 它是 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]; + } + } + + // 通过 name 获取 product + if (result) { + return result; + } + + // 通过 type 获取 products + if (prop in types) { + return types[prop]; + } + + // 获取 product type + 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="一个完整的_traps_列表示例">一个完整的 <code>traps</code> 列表示例</h3> + +<p>出于教学目的,这里为了创建一个完整的 traps 列表示例,我们将尝试代理化一个非原生对象,这特别适用于这类操作:由 <a href="/zh-CN/docs/DOM/document.cookie#A_little_framework.3A_a_complete_cookies_reader.2Fwriter_with_full_unicode_support">发布在 document.cookie页面上的“小型框架”</a>创建的<code>docCookies</code>全局对象。</p> + +<pre class="brush: js notranslate">/* + var docCookies = ... get the "docCookies" object here: + https://developer.mozilla.org/zh-CN/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(); + }, + "ownKeys": function (oTarget, sKey) { + return oTarget.keys(); + }, + "has": function (oTarget, sKey) { + return sKey in oTarget || oTarget.hasItem(sKey); + }, + "defineProperty": function (oTarget, sKey, oDesc) { + if (oDesc && "value" in oDesc) { oTarget.setItem(sKey, oDesc.value); } + return oTarget; + }, + "getOwnPropertyDescriptor": function (oTarget, sKey) { + var vValue = oTarget.getItem(sKey); + return vValue ? { + "value": vValue, + "writable": true, + "enumerable": true, + "configurable": false + } : undefined; + }, +}); + +/* 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="规范">规范</h2> + +<table class="standard-table"> + <tbody> + <tr> + <th scope="col">Specification</th> + <th scope="col">Status</th> + <th scope="col">Comment</th> + </tr> + <tr> + <td>{{SpecName('ES2015', '#sec-proxy-objects', 'Proxy')}}</td> + <td>{{Spec2('ES2015')}}</td> + <td>Initial definition.</td> + </tr> + <tr> + <td>{{SpecName('ES2016', '#sec-proxy-objects', 'Proxy')}}</td> + <td>{{Spec2('ES2016')}}</td> + <td></td> + </tr> + <tr> + <td>{{SpecName('ES2017', '#sec-proxy-objects', 'Proxy')}}</td> + <td>{{Spec2('ES2017')}}</td> + <td></td> + </tr> + <tr> + <td>{{SpecName('ESDraft', '#sec-proxy-objects', 'Proxy')}}</td> + <td>{{Spec2('ESDraft')}}</td> + <td></td> + </tr> + </tbody> +</table> + +<h2 id="浏览器兼容性">浏览器兼容性</h2> + +<div class="hidden">The compatibility table on this page is generated from structured data. If you'd like to contribute to the data, please check out <a href="https://github.com/mdn/browser-compat-data">https://github.com/mdn/browser-compat-data</a> and send us a pull request.</div> + +<p>{{Compat("javascript.builtins.Proxy", 2)}}</p> + +<h2 id="参考">参考</h2> + +<ul> + <li><a class="external" href="http://jsconf.eu/2010/speaker/be_proxy_objects.html">"Proxies are awesome" Brendan Eich presentation at JSConf</a> (<a class="external" href="http://www.slideshare.net/BrendanEich/metaprog-5303821">slides</a>)</li> + <li><a class="external" href="http://wiki.ecmascript.org/doku.php?id=harmony:proxies">ECMAScript Harmony Proxy proposal page</a> and <a class="external" href="http://wiki.ecmascript.org/doku.php?id=harmony:proxies_semantics">ECMAScript Harmony proxy semantics page</a></li> + <li><a class="external" href="http://soft.vub.ac.be/~tvcutsem/proxies/">Tutorial on proxies</a></li> + <li><a href="/zh-CN/docs/JavaScript/Old_Proxy_API">SpiderMonkey specific Old Proxy API</a></li> + <li>{{jsxref("Object.watch()")}} is a non-standard feature but has been supported in Gecko for a long time.</li> +</ul> + +<h2 id="版权声明">版权声明</h2> + +<p>一些内容(如文本、例子)是复制自或修改自<a class="external" href="http://wiki.ecmascript.org/doku.php">ECMAScript wiki</a>(版权声明 <a class="external" href="http://creativecommons.org/licenses/by-nc-sa/2.0/">CC 2.0 BY-NC-SA</a>)。</p> |
