diff options
Diffstat (limited to 'files/zh-tw/web/javascript/reference/global_objects/proxy')
-rw-r--r-- | files/zh-tw/web/javascript/reference/global_objects/proxy/index.html | 400 |
1 files changed, 400 insertions, 0 deletions
diff --git a/files/zh-tw/web/javascript/reference/global_objects/proxy/index.html b/files/zh-tw/web/javascript/reference/global_objects/proxy/index.html new file mode 100644 index 0000000000..54a71be888 --- /dev/null +++ b/files/zh-tw/web/javascript/reference/global_objects/proxy/index.html @@ -0,0 +1,400 @@ +--- +title: Proxy +slug: Web/JavaScript/Reference/Global_Objects/Proxy +tags: + - Class + - ECMAScript 2015 + - JavaScript + - Proxy +translation_of: Web/JavaScript/Reference/Global_Objects/Proxy +--- +<div> +<div>{{JSRef}}</div> +</div> + +<p><strong>Proxy</strong> 物件被使用於定義基本操作的自定行為(例如:尋找屬性、賦值、列舉、函式調用等等)。</p> + +<h2 id="術語">術語</h2> + +<dl> + <dt><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler">handler</a></dt> + <dd>Placeholder object which contains traps.</dd> + <dt>traps</dt> + <dd>The methods that provide property access. This is analogous to the concept of traps in operating systems.</dd> + <dt>target</dt> + <dd>Object which the proxy virtualizes. It is often used as storage backend for the proxy. Invariants (semantics that remain unchanged) regarding object non-extensibility or non-configurable properties are verified against the target.</dd> +</dl> + +<h2 id="語法">語法</h2> + +<pre class="syntaxbox notranslate">var p = new Proxy(target, handler); +</pre> + +<h3 id="參數">參數</h3> + +<dl> + <dt><code>target</code></dt> + <dd>A target object (can be any sort of object, including a native array, a function or even another proxy) to wrap with <code>Proxy</code>.</dd> + <dt><code>handler</code></dt> + <dd>An object whose properties are functions which define the behavior of the proxy when an operation is performed on it.</dd> +</dl> + +<h2 id="方法">方法</h2> + +<dl> + <dt>{{jsxref("Proxy.revocable()")}}</dt> + <dd>Creates a revocable <code>Proxy</code> object.</dd> +</dl> + +<h2 id="Methods_of_the_handler_object">Methods of the handler object</h2> + +<p>The handler object is a placeholder object which contains traps for <code>Proxy</code>.</p> + +<div>{{page('/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler', 'Methods') }}</div> + +<h2 id="範例">範例</h2> + +<h3 id="Basic_example">Basic example</h3> + +<p>In this simple example the number <code>37</code> gets returned as the default value when the property name is not in the object. It is using the <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/get"><code>get</code></a> handler.</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="No-op_forwarding_proxy">No-op forwarding proxy</h3> + +<p>In this example, we are using a native JavaScript object to which our proxy will forward all operations that are applied to it.</p> + +<pre class="brush: js notranslate">var target = {}; +var p = new Proxy(target, {}); + +p.a = 37; // operation forwarded to the target + +console.log(target.a); // 37. The operation has been properly forwarded +</pre> + +<h3 id="Validation">Validation</h3> + +<p>With a <code>Proxy</code>, you can easily validate the passed value for an object. This example uses the <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/set"><code>set</code></a> 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; + + // Indicate success + return true; + } +}; + +let person = new Proxy({}, validator); + +person.age = 100; +console.log(person.age); // 100 +person.age = 'young'; // Throws an exception +person.age = 300; // Throws an exception</pre> + +<h3 id="Extending_constructor">Extending constructor</h3> + +<p>A function proxy could easily extend a constructor with a new constructor. This example uses the <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/construct"><code>construct</code></a> and <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/apply"><code>apply</code></a> handlers.</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="Manipulating_DOM_nodes">Manipulating DOM nodes</h3> + +<p>Sometimes you want to toggle the attribute or class name of two different elements. Here's how using the <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/set"><code>set</code></a> 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'); + } + } + + // The default behavior to store the value + obj[prop] = newval; + + // Indicate success + 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="Value_correction_and_an_extra_property">Value correction and an extra property</h3> + +<p>The <code>products</code> proxy object evaluates the passed value and convert it to an array if needed. The object also supports an extra property called <code>latestBrowser</code> both as a getter and a setter.</p> + +<pre class="brush: js notranslate">let products = new Proxy({ + browsers: ['Internet Explorer', 'Netscape'] +}, +{ + get: function(obj, prop) { + // An extra property + if (prop === 'latestBrowser') { + return obj.browsers[obj.browsers.length - 1]; + } + + // The default behavior to return the value + return obj[prop]; + }, + set: function(obj, prop, value) { + // An extra property + if (prop === 'latestBrowser') { + obj.browsers.push(value); + return true; + } + + // Convert the value if it is not an array + if (typeof value === 'string') { + value = [value]; + } + + // The default behavior to store the value + obj[prop] = value; + + // Indicate success + return true; + } +}); + +console.log(products.browsers); // ['Internet Explorer', 'Netscape'] +products.browsers = 'Firefox'; // pass a string (by mistake) +console.log(products.browsers); // ['Firefox'] <- no problem, the value is an array + +products.latestBrowser = 'Chrome'; +console.log(products.browsers); // ['Firefox', 'Chrome'] +console.log(products.latestBrowser); // 'Chrome'</pre> + +<h3 id="Finding_an_array_item_object_by_its_property">Finding an array item object by its property</h3> + +<p>This proxy extends an array with some utility features. As you see, you can flexibly "define" properties without using <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties"><code>Object.defineProperties</code></a>. This example can be adapted to find a table row by its cell. In that case, the target will be <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) { + // The default behavior to return the value; prop is usually an integer + if (prop in obj) { + return obj[prop]; + } + + // Get the number of products; an alias of 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]; + } + } + + // Get a product by name + if (result) { + return result; + } + + // Get products by type + if (prop in types) { + return types[prop]; + } + + // Get product types + 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="A_complete_traps_list_example">A complete <code>traps</code> list example</h3> + +<p>Now in order to create a complete sample <code>traps</code> list, for didactic purposes, we will try to proxify a <em>non native</em> object that is particularly suited to this type of operation: the <code>docCookies</code> global object created by <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie/Simple_document.cookie_framework" title="https://developer.mozilla.org/en-US/docs/DOM/document.cookie#A_little_framework.3A_a_complete_cookies_reader.2Fwriter_with_full_unicode_support">the "little framework" published on the <code>document.cookie</code> page</a>.</p> + +<pre class="brush: js notranslate">/* + var docCookies = ... get the "docCookies" object here: + 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(); + }, + 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 test */ + +console.log(docCookies.my_cookie1 = 'First value'); +console.log(docCookies.getItem('my_cookie1')); + +docCookies.setItem('my_cookie1', 'Changed value'); +console.log(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('ESDraft', '#sec-proxy-objects', 'Proxy')}}</td> + <td>{{Spec2('ESDraft')}}</td> + <td></td> + </tr> + </tbody> +</table> + +<h2 id="瀏覽器相容性">瀏覽器相容性</h2> + +<p>{{Compat("javascript.builtins.Proxy", 2)}}</p> + +<h2 id="Gecko_specific_notes">Gecko specific notes</h2> + +<ul> + <li>At present, <code>Object.getPrototypeOf(proxy)</code> unconditionally returns <code>Object.getPrototypeOf(target)</code>, because the ES2015 getPrototypeOf trap is not yet implemented ({{bug(795904)}}, {{bug(888969)}}).</li> + <li><code>Array.isArray(proxy)</code> unconditionally returns <code>Array.isArray(target)</code> ({{bug(1096753)}}, {{bug(1111785)}}).</li> + <li><code>Object.prototype.toString.call(proxy)</code> unconditionally returns <code>Object.prototype.toString.call(target)</code>, because ES2015 Symbol.toStringTag is not yet implemented ({{bug(1114580)}}).</li> +</ul> + +<h2 id="參見">參見</h2> + +<ul> + <li><a class="external" href="https://www.youtube.com/watch?v=sClk6aB_CPk">"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="/en-US/docs/JavaScript/Old_Proxy_API" title="/en-US/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="Licensing_note">Licensing note</h2> + +<p>Some content (text, examples) in this page has been copied or adapted from the <a class="external" href="http://wiki.ecmascript.org/doku.php">ECMAScript wiki</a> which content is licensed <a class="external" href="http://creativecommons.org/licenses/by-nc-sa/2.0/">CC 2.0 BY-NC-SA</a>.</p> |