aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/web/javascript/guide/meta_programming/index.html
blob: f5cd4d7ec2d794e686e82eff300efcd855130bb3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
---
title: 元编程
slug: Web/JavaScript/Guide/Meta_programming
tags:
  - Guide
  - JavaScript
  - Proxy
  - Reflect
translation_of: Web/JavaScript/Guide/Meta_programming
---
<div>{{jsSidebar("JavaScript Guide")}} {{PreviousNext("Web/JavaScript/Guide/Iterators_and_Generators", "Web/JavaScript/Guide/Modules")}}</div>

<p>从ECMAScript 2015 开始,JavaScript 获得了 {{jsxref("Proxy")}}{{jsxref("Reflect")}} 对象的支持,允许你拦截并定义基本语言操作的自定义行为(例如,属性查找,赋值,枚举,函数调用等)。借助这两个对象,你可以在 JavaScript 元级别进行编程。</p>

<h2 id="代理">代理</h2>

<p>在 ECMAScript 6 中引入的 {{jsxref("Proxy")}} 对象可以拦截某些操作并实现自定义行为。例如获取一个对象上的属性:</p>

<pre class="brush: js">let handler = {
  get: function(target, name){
    return name in target ? target[name] : 42;
}};

let p = new Proxy({}, handler);
p.a = 1;

console.log(p.a, p.b); // 1, 42
</pre>

<p><code>Proxy</code> 对象定义了一个目标(这里是一个空对象)和一个实现了 <code>get</code> 陷阱的 handler 对象。这里,代理的对象在获取未定义的属性时不会返回 <code>undefined</code>,而是返回 42。</p>

<p>更多例子参见 {{jsxref("Proxy")}} 页面 。</p>

<h3 id="术语">术语</h3>

<p>在讨论代理的功能时会用到以下术语。</p>

<dl>
 <dt>{{jsxref("Global_Objects/Proxy/handler","handler")}}</dt>
 <dd>包含陷阱的占位符对象。</dd>
 <dt>traps</dt>
 <dd>提供属性访问的方法。这类似于操作系统中陷阱的概念。</dd>
 <dt>target</dt>
 <dd>代理虚拟化的对象。它通常用作代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)。</dd>
 <dt>invariants</dt>
 <dd>实现自定义操作时保持不变的语义称为不变量。如果你违反处理程序的不变量,则会抛出一个 {{jsxref("TypeError")}}</dd>
</dl>

<h2 id="句柄和陷阱">句柄和陷阱</h2>

<p>以下表格中总结了 <code>Proxy</code> 对象可用的陷阱。详细的解释和例子请看<a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler">参考页</a></p>

<table class="standard-table">
 <thead>
  <tr>
   <th>Handler / trap</th>
   <th>Interceptions</th>
   <th>Invariants</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>{{jsxref("Global_Objects/Proxy/handler/getPrototypeOf", "handler.getPrototypeOf()")}}</td>
   <td>{{jsxref("Object.getPrototypeOf()")}}<br>
    {{jsxref("Reflect.getPrototypeOf()")}}<br>
    {{jsxref("Object/proto", "__proto__")}}<br>
    {{jsxref("Object.prototype.isPrototypeOf()")}}<br>
    {{jsxref("Operators/instanceof", "instanceof")}}</td>
   <td>
    <ul>
     <li><code>getPrototypeOf</code>方法一定返回一个对象或<code>null</code>.</li>
     <li>如果 <code>target</code> 不可扩展,<code>Object.getPrototypeOf(proxy)</code> 必须返回和 <code>Object.getPrototypeOf(target)</code>一样的值。</li>
    </ul>
   </td>
  </tr>
  <tr>
   <td>{{jsxref("Global_Objects/Proxy/handler/setPrototypeOf", "handler.setPrototypeOf()")}}</td>
   <td>{{jsxref("Object.setPrototypeOf()")}}<br>
    {{jsxref("Reflect.setPrototypeOf()")}}</td>
   <td>如果 <code>target</code> 不可扩展,<code>prototype</code> 参数必须与<code>Object.getPrototypeOf(target)</code>的值相同。</td>
  </tr>
  <tr>
   <td>{{jsxref("Global_Objects/Proxy/handler/isExtensible", "handler.isExtensible()")}}</td>
   <td>{{jsxref("Object.isExtensible()")}}<br>
    {{jsxref("Reflect.isExtensible()")}}</td>
   <td><code>Object.isExtensible(proxy)</code> 必须返回和<code>Object.isExtensible(target)</code>一样的值。</td>
  </tr>
  <tr>
   <td>{{jsxref("Global_Objects/Proxy/handler/preventExtensions", "handler.preventExtensions()")}}</td>
   <td>{{jsxref("Object.preventExtensions()")}}<br>
    {{jsxref("Reflect.preventExtensions()")}}</td>
   <td> 如果<code>Object.isExtensible(proxy)</code> 值为 <code>false,Object.preventExtensions(proxy)</code> 只返回<code>true。</code></td>
  </tr>
  <tr>
   <td>{{jsxref("Global_Objects/Proxy/handler/getOwnPropertyDescriptor", "handler.getOwnPropertyDescriptor()")}}</td>
   <td>{{jsxref("Object.getOwnPropertyDescriptor()")}}<br>
    {{jsxref("Reflect.getOwnPropertyDescriptor()")}}</td>
   <td>
    <ul>
     <li><code>getOwnPropertyDescripton</code> 只能返回对象或者<code>undefined</code>.</li>
     <li>A property cannot be reported as non-existent, if it exists as a non-configurable own property of the target object.</li>
     <li>A property cannot be reported as non-existent, if it exists as an own property of the target object and the target object is not extensible.</li>
     <li>A property cannot be reported as existent, if it does not exists as an own property of the target object and the target object is not extensible.</li>
     <li>A property cannot be reported as non-configurable, if it does not exists as an own property of the target object or if it exists as a configurable own property of the target object.</li>
     <li>The result of <code>Object.getOwnPropertyDescriptor(target)</code> can be applied to the target object using <code>Object.defineProperty</code> and will not throw an exception.</li>
    </ul>
   </td>
  </tr>
  <tr>
   <td>{{jsxref("Global_Objects/Proxy/handler/defineProperty", "handler.defineProperty()")}}</td>
   <td>{{jsxref("Object.defineProperty()")}}<br>
    {{jsxref("Reflect.defineProperty()")}}</td>
   <td>
    <ul>
     <li>A property cannot be added, if the target object is not extensible.</li>
     <li>A property cannot be added as or modified to be non-configurable, if it does not exists as a non-configurable own property of the target object.</li>
     <li>A property may not be non-configurable, if a corresponding configurable property of the target object exists.</li>
     <li>If a property has a corresponding target object property then <code>Object.defineProperty(target, prop, descriptor)</code> will not throw an exception.</li>
     <li>In strict mode, a <code>false</code> return value from the <code>defineProperty</code> handler will throw a {{jsxref("TypeError")}} exception.</li>
    </ul>
   </td>
  </tr>
  <tr>
   <td>{{jsxref("Global_Objects/Proxy/handler/has", "handler.has()")}}</td>
   <td>Property query: <code>foo in proxy</code><br>
    Inherited property query: <code>foo in Object.create(proxy)</code><br>
    {{jsxref("Reflect.has()")}}</td>
   <td>
    <ul>
     <li>A property cannot be reported as non-existent, if it exists as a non-configurable own property of the target object.</li>
     <li>A property cannot be reported as non-existent, if it exists as an own property of the target object and the target object is not extensible.</li>
    </ul>
   </td>
  </tr>
  <tr>
   <td>{{jsxref("Global_Objects/Proxy/handler/get", "handler.get()")}}</td>
   <td>Property access: <code>proxy[foo]</code>and <code>proxy.bar</code><br>
    Inherited property access: <code>Object.create(proxy)[foo]</code><br>
    {{jsxref("Reflect.get()")}}</td>
   <td>
    <ul>
     <li>The value reported for a property must be the same as the value of the corresponding target object property if the target object property is a non-writable, non-configurable data property.</li>
     <li>The value reported for a property must be undefined if the corresponding target object property is non-configurable accessor property that has undefined as its [[Get]] attribute.</li>
    </ul>
   </td>
  </tr>
  <tr>
   <td>{{jsxref("Global_Objects/Proxy/handler/set", "handler.set()")}}</td>
   <td>Property assignment: <code>proxy[foo] = bar</code> and <code>proxy.foo = bar</code><br>
    Inherited property assignment: <code>Object.create(proxy)[foo] = bar</code><br>
    {{jsxref("Reflect.set()")}}</td>
   <td>
    <ul>
     <li>Cannot change the value of a property to be different from the value of the corresponding target object property if the corresponding target object property is a non-writable, non-configurable data property.</li>
     <li>Cannot set the value of a property if the corresponding target object property is a non-configurable accessor property that has <code>undefined</code> as its [[Set]] attribute.</li>
     <li>In strict mode, a <code>false</code> return value from the <code>set</code> handler will throw a {{jsxref("TypeError")}} exception.</li>
    </ul>
   </td>
  </tr>
  <tr>
   <td>{{jsxref("Global_Objects/Proxy/handler/deleteProperty", "handler.deleteProperty()")}}</td>
   <td>Property deletion: <code>delete proxy[foo]</code> and <code>delete proxy.foo</code><br>
    {{jsxref("Reflect.deleteProperty()")}}</td>
   <td>A property cannot be deleted, if it exists as a non-configurable own property of the target object.</td>
  </tr>
  <tr>
   <td>{{jsxref("Global_Objects/Proxy/handler/enumerate", "handler.enumerate()")}}</td>
   <td>Property enumeration / for...in: <code>for (var name in proxy) {...}</code><br>
    {{jsxref("Reflect.enumerate()")}}</td>
   <td>The <code>enumerate</code> method must return an object.</td>
  </tr>
  <tr>
   <td>{{jsxref("Global_Objects/Proxy/handler/ownKeys", "handler.ownKeys()")}}</td>
   <td>{{jsxref("Object.getOwnPropertyNames()")}}<br>
    {{jsxref("Object.getOwnPropertySymbols()")}}<br>
    {{jsxref("Object.keys()")}}<br>
    {{jsxref("Reflect.ownKeys()")}}</td>
   <td>
    <ul>
     <li>The result of <code>ownKeys</code> is a List.</li>
     <li>The Type of each result List element is either {{jsxref("String")}} or {{jsxref("Symbol")}}.</li>
     <li>The result List must contain the keys of all non-configurable own properties of the target object.</li>
     <li>If the target object is not extensible, then the result List must contain all the keys of the own properties of the target object and no other values.</li>
    </ul>
   </td>
  </tr>
  <tr>
   <td>{{jsxref("Global_Objects/Proxy/handler/apply", "handler.apply()")}}</td>
   <td><code>proxy(..args)</code><br>
    {{jsxref("Function.prototype.apply()")}} and {{jsxref("Function.prototype.call()")}}<br>
    {{jsxref("Reflect.apply()")}}</td>
   <td>There are no invariants for the <code>handler.apply</code> method.</td>
  </tr>
  <tr>
   <td>{{jsxref("Global_Objects/Proxy/handler/construct", "handler.construct()")}}</td>
   <td><code>new proxy(...args)</code><br>
    {{jsxref("Reflect.construct()")}}</td>
   <td>结果一定是一个<code>Object</code></td>
  </tr>
 </tbody>
</table>

<h2 id="撤销_Proxy">撤销 <code>Proxy</code></h2>

<p>{{jsxref("Proxy.revocable()")}} 方法被用来创建可撤销的 <code>Proxy</code> 对象。这意味着 proxy 可以通过 <code>revoke</code> 函数来撤销,并且关闭代理。此后,代理上的任意的操作都会导致{{jsxref("TypeError")}}</p>

<pre class="brush: js">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 is thrown
proxy.foo = 1           // TypeError again
delete proxy.foo;       // still TypeError
typeof proxy            // "object", typeof doesn't trigger any trap</pre>

<h2 id="反射">反射</h2>

<p>{{jsxref("Reflect")}} 是一个内置对象,它提供了可拦截 JavaScript 操作的方法。该方法和{{jsxref("Global_Objects/Proxy/handler","代理句柄")}}类似,但 <code>Reflect</code> 方法并不是一个函数对象。</p>

<p><code>Reflect</code> 有助于将默认操作从处理程序转发到目标。</p>

<p>{{jsxref("Reflect.has()")}} 为例,你可以将 <a href="/en-US/docs/Web/JavaScript/Reference/Operators/in"><code>in</code> 运算符</a>作为函数:</p>

<pre class="brush: js">Reflect.has(Object, "assign"); // true
</pre>

<h3 id="更好的_apply_函数">更好的 <code>apply</code> 函数</h3>

<p>在 ES5 中,我们通常使用 {{jsxref("Function.prototype.apply()")}} 方法调用一个具有给定 <code>this</code> 值和 <code>arguments</code> 数组(或<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Indexed_collections#Working_with_array-like_objects">类数组对象</a>)的函数。</p>

<pre class="brush: js">Function.prototype.apply.call(Math.floor, undefined, [1.75]);</pre>

<p>使用 {{jsxref("Reflect.apply")}},这变得不那么冗长和容易理解:</p>

<pre class="brush: js">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"</pre>

<h3 id="检查属性定义是否成功">检查属性定义是否成功</h3>

<p>使用 {{jsxref("Object.defineProperty")}},如果成功返回一个对象,否则抛出一个 {{jsxref("TypeError")}},你将使用 {{jsxref("Statements/try...catch","try...catch")}} 块来捕获定义属性时发生的任何错误。因为 {{jsxref("Reflect.defineProperty")}} 返回一个布尔值表示的成功状态,你可以在这里使用 {{jsxref("Statements/if...else","if...else")}} 块:</p>

<pre class="brush: js">if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}
</pre>

<p>{{Previous("Web/JavaScript/Guide/Iterators_and_Generators")}}</p>