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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
|
---
title: メタプログラミング
slug: Web/JavaScript/Guide/Meta_programming
tags:
- ECMAScript 2015
- Guide
- JavaScript
- Proxy
- Reflect
- l10n:priority
translation_of: Web/JavaScript/Guide/Meta_programming
---
<div>{{jsSidebar("JavaScript Guide")}}{{PreviousNext("Web/JavaScript/Guide/Iterators_and_Generators", "Web/JavaScript/Guide/Modules")}}</div>
<p class="summary">ECMAScript 2015 から、JavaScript には {{jsxref("Proxy")}} オブジェクトと {{jsxref("Reflect")}} オブジェクトがサポートされました。これらは基本的な言語操作 (例えば、プロパティ参照、代入、列挙、関数呼び出しなど) に割り込み、動作をカスタマイズすることができます。この 2 つのオブジェクトのおかげで、JavaScript でメタレベルのプログラミングが行えます。</p>
<h2 id="Proxies">プロキシー</h2>
<p>ECMAScript 6 で導入された {{jsxref("Proxy")}} オブジェクトによって、特定の操作に割り込んで動作をカスタマイズすることができます。</p>
<p>例えば、オブジェクトのプロパティを取得してみましょう。</p>
<pre class="brush: js">let <var>handler</var> = {
get: function(<var>target</var>, name) {
return name in <var>target</var> ? <var>target</var>[name] : 42
}
}
let p = new Proxy({}, <var>handler</var>)
p.a = 1
console.log(p.a, p.b) // 1, 42
</pre>
<p>この <code>Proxy</code> オブジェクトは <dfn><code><var>target</var></code></dfn> (ここでは空オブジェクト) と <dfn><code><var>handler</var></code></dfn> オブジェクトを定義し、その中に <code>get</code> トラップが実装されています。ここで、プロキシーとなったオブジェクトは未定義のプロパティを取得しようとした時に <code>undefined</code> を返さず、代わりに数値 <code>42</code> を返します。</p>
<p>それ以外の例は {{jsxref("Proxy")}} のリファレンスページを参照してください。</p>
<h3 id="Terminology">用語集</h3>
<p>プロキシーの機能について話題にする際は、次の用語が使用されます。</p>
<dl>
<dt>{{jsxref("Global_Objects/Proxy/Proxy","ハンドラー","","true")}} (handler)</dt>
<dd>トラップを入れるためのプレースホルダ用オブジェクト。</dd>
<dt>トラップ (trap)</dt>
<dd>プロパティへのアクセスを提供するメソッドです。 (オペレーティングシステムにおける<em>トラップ</em>の概念と同じようなものです。)</dd>
<dt>ターゲット (<var>target</var>)</dt>
<dd>プロキシーが仮想化するオブジェクトです。多くの場合、プロキシーのストレージバックエンドとして使用されます。拡張や設定できないオブジェクトのプロパティの不変条件 (変更されない意味) がターゲットに対して検証されます。</dd>
<dt>不変条件 (invariant)</dt>
<dd>独自の操作を実装した際に変更されない意味を<strong>不変条件</strong>と呼びます。ハンドラーの不変条件に違反した場合、 {{jsxref("TypeError")}} が発生します。</dd>
</dl>
<h2 id="Handlers_and_traps">ハンドラーとトラップ</h2>
<p>次の表は、 <code>Proxy</code> オブジェクトに対して利用可能なトラップをまとめたものです。詳細な説明と例については、<a href="/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy">リファレンスページ</a>を参照してください。</p>
<table class="standard-table">
<thead>
<tr>
<th>ハンドラー / トラップ</th>
<th>割り込みされる処理</th>
<th>不変条件</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{jsxref("Global_Objects/Proxy/Proxy/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><var>target</var></code> が拡張できない場合、<code>Object.getPrototypeOf(<var>proxy</var>)</code> メソッドは <code>Object.getPrototypeOf(<var>target</var>)</code> と同じ値を返す必要があります。</li>
</ul>
</td>
</tr>
<tr>
<td>{{jsxref("Global_Objects/Proxy/Proxy/setPrototypeOf", "handler.setPrototypeOf()")}}</td>
<td>{{jsxref("Object.setPrototypeOf()")}}<br>
{{jsxref("Reflect.setPrototypeOf()")}}</td>
<td><code><var>target</var></code> が拡張できない場合、<code>prototype</code> パラメータは <code>Object.getPrototypeOf(<var>target</var>)</code> と同じ値である必要があります。</td>
</tr>
<tr>
<td>{{jsxref("Global_Objects/Proxy/Proxy/isExtensible", "handler.isExtensible()")}}</td>
<td>{{jsxref("Object.isExtensible()")}}<br>
{{jsxref("Reflect.isExtensible()")}}</td>
<td><code>Object.isExtensible(<var>proxy</var>)</code> は <code>Object.isExtensible(<var>target</var>)</code> と同じ値を返す必要があります。</td>
</tr>
<tr>
<td>{{jsxref("Global_Objects/Proxy/Proxy/preventExtensions", "handler.preventExtensions()")}}</td>
<td>{{jsxref("Object.preventExtensions()")}}<br>
{{jsxref("Reflect.preventExtensions()")}}</td>
<td>
<p><code>Object.isExtensible(<var>proxy</var>)</code> が <code>false</code> の場合のみ、<code>Object.preventExtensions(<var>proxy</var>)</code> は <code>true</code> を返します。</p>
</td>
</tr>
<tr>
<td>{{jsxref("Global_Objects/Proxy/Proxy/getOwnPropertyDescriptor", "handler.getOwnPropertyDescriptor()")}}</td>
<td>{{jsxref("Object.getOwnPropertyDescriptor()")}}<br>
{{jsxref("Reflect.getOwnPropertyDescriptor()")}}</td>
<td>
<ul>
<li><code>getOwnPropertyDescriptor</code> はオブジェクトか <code>undefined</code> のいずれかを返す必要があります。</li>
<li>ターゲットオブジェクトに設定不可の所有プロパティとして存在する場合、そのプロパティについて存在しないと報告することはできません。</li>
<li>拡張不可のターゲットオブジェクトに所有プロパティとして存在する場合、そのプロパティについて存在しないと報告することはできません。</li>
<li>拡張不可のターゲットオブジェクトに所有プロパティとして存在しない場合、そのプロパティについて存在すると報告することはできません。</li>
<li>ターゲットオブジェクトに所有プロパティとして存在しない場合、あるいはターゲットオブジェクトに設定可能な所有プロパティとして存在する場合、そのプロパティについて設定不可と報告することはできません。</li>
<li><code>Object.getOwnPropertyDescriptor(<var>target</var>)</code> の結果は <code>Object.defineProperty</code> を使用してターゲットオブジェクトに適用され、この時に例外は発生しません。</li>
</ul>
</td>
</tr>
<tr>
<td>{{jsxref("Global_Objects/Proxy/Proxy/defineProperty", "handler.defineProperty()")}}</td>
<td>{{jsxref("Object.defineProperty()")}}<br>
{{jsxref("Reflect.defineProperty()")}}</td>
<td>
<ul>
<li>ターゲットオブジェクトが拡張可能ではない場合、プロパティは追加できません。</li>
<li>ターゲットオブジェクトに設定不可の所有プロパティとして存在しない場合、そのプロパティを追加したり、また設定不可に更新することはできません。</li>
<li>ターゲットオブジェクトに対応する設定可能なプロパティとして存在する場合、そのプロパティを設定不可としてもかまいません。</li>
<li>プロパティが対応するターゲットオブジェクトプロパティを持つ場合、<code>Object.defineProperty(<var>target</var>, <var>prop</var>, <var>descriptor</var>)</code> は例外を発生しません。</li>
<li>strict モードでは、<code>defineProperty</code> ハンドラーからの返値が <code>false</code> の場合、{{jsxref("TypeError")}} 例外が発生します。</li>
</ul>
</td>
</tr>
<tr>
<td>{{jsxref("Global_Objects/Proxy/Proxy/has", "handler.has()")}}</td>
<td>
<dl>
<dt>プロパティの照会</dt>
<dd><code>foo in proxy</code></dd>
<dt>継承されたプロパティの照会</dt>
<dd><code>foo in Object.create(<var>proxy</var>)</code><br>
{{jsxref("Reflect.has()")}}</dd>
</dl>
</td>
<td>
<ul>
<li>ターゲットオブジェクトに設定不可の所有プロパティとして存在する場合、そのプロパティについて存在しないと報告することはできません。</li>
<li>ターゲットオブジェクトの所有プロパティとして存在し、そのターゲットオブジェクトが拡張可能ではない場合、そのプロパティについて存在しないと報告することはできません。</li>
</ul>
</td>
</tr>
<tr>
<td>{{jsxref("Global_Objects/Proxy/Proxy/get", "handler.get()")}}</td>
<td>
<dl>
<dt>プロパティへのアクセス</dt>
<dd><code><var>proxy</var>[foo]</code><br>
<code><var>proxy</var>.bar</code></dd>
<dt>継承されたプロパティへのアクセス</dt>
<dd><code>Object.create(<var>proxy</var>)[foo]</code><br>
{{jsxref("Reflect.get()")}}</dd>
</dl>
</td>
<td>
<ul>
<li>ターゲットオブジェクトプロパティが書込不可、設定不可のデータプロパティである場合、プロパティに対して報告する値は対応するプロパティと同じ値である必要があります。</li>
<li>対応するターゲットオブジェクトプロパティが、Get 属性に <code>undefined</code> を持つ設定不可のアクセサプロパティである場合、プロパティに対して報告される値を <code>undefined</code> とする必要があります。</li>
</ul>
</td>
</tr>
<tr>
<td>{{jsxref("Global_Objects/Proxy/Proxy/set", "handler.set()")}}</td>
<td>
<dl>
<dt>プロパティへの代入</dt>
<dd><code><var>proxy</var>[foo] = bar</code><br>
<code><var>proxy</var>.foo = bar</code></dd>
<dt>継承されたプロパティへの代入</dt>
<dd><code>Object.create(<var>proxy</var>)[foo] = bar</code><br>
{{jsxref("Reflect.set()")}}</dd>
</dl>
</td>
<td>
<ul>
<li>対応するターゲットオブジェクトのプロパティが書込不可、設定不可のデータプロパティである場合、そのプロパティとは違うプロパティ値に変更することはできません。</li>
<li>対応するターゲットオブジェクトプロパティが、Set 属性に <code>undefined</code> を持つ設定不可のアクセサプロパティである場合、プロパティの値を設定することはできません。</li>
<li>strict モードでは、 <code>false</code> という返値が <code>set</code> ハンドラーから返された場合、{{jsxref("TypeError")}} 例外が発生します。
<ul>
</ul>
</li>
</ul>
</td>
</tr>
<tr>
<td>{{jsxref("Global_Objects/Proxy/Proxy/deleteProperty", "handler.deleteProperty()")}}</td>
<td>
<dl>
<dt>プロパティの削除</dt>
<dd><code>delete <var>proxy</var>[foo]</code><br>
<code>delete <var>proxy</var>.foo</code><br>
{{jsxref("Reflect.deleteProperty()")}}</dd>
</dl>
</td>
<td><code><var>target</var></code> に構成不可の所有プロパティとして存在する場合、削除することはできません。</td>
</tr>
<tr>
<td>{{jsxref("Global_Objects/Proxy/enumerate", "handler.enumerate()")}}</td>
<td>
<dl>
<dt>プロパティの列挙 / <code>for...in</code>:</dt>
<dd><code>for (let name in <var>proxy</var>) {...}</code><br>
{{jsxref("Reflect.enumerate()")}}</dd>
</dl>
</td>
<td><code>enumerate</code> メソッドはオブジェクトを返す必要があります。</td>
</tr>
<tr>
<td>{{jsxref("Global_Objects/Proxy/Proxy/ownKeys", "handler.ownKeys()")}}</td>
<td>{{jsxref("Object.getOwnPropertyNames()")}}<br>
{{jsxref("Object.getOwnPropertySymbols()")}}<br>
{{jsxref("Object.keys()")}}<br>
{{jsxref("Reflect.ownKeys()")}}</td>
<td>
<ul>
<li><code>ownKeys</code> の結果はリストとなります。</li>
<li>出力リストの要素の型は {{jsxref("String")}} か {{jsxref("Symbol")}} のどちらかとなります。</li>
<li>出力リストは <code><var>target</var></code> のすべての設定不可の所有プロパティのキーを含める必要があります。</li>
<li>ターゲットオブジェクトが拡張できない場合、出力リストはターゲットオブジェクト中の所有プロパティのキーをすべて含める必要があり、他の値は含まれません。
</li>
</ul>
</td>
</tr>
<tr>
<td>{{jsxref("Global_Objects/Proxy/Proxy/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><code><var>handler</var>.apply</code> メソッドに対する不変条件はありません。</td>
</tr>
<tr>
<td>{{jsxref("Global_Objects/Proxy/Proxy/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="Revocable_Proxy">取り消し可能 <code>Proxy</code></h2>
<p>{{jsxref("Proxy.revocable()")}} メソッドは取り消し可能な <code>Proxy</code> オブジェクトの生成に使用されます。これにより、プロキシーを <code>revoke</code> 関数で取り消し、プロキシーの機能を停止することができます。</p>
<p>その後はプロキシーを通じたいかなる操作も {{jsxref("TypeError")}} になります。</p>
<pre class="brush: js">let revocable = Proxy.revocable({}, {
get: function(target, name) {
return '[[' + name + ']]'
}
})
let proxy = revocable.proxy
console.log(proxy.foo) // "[[foo]]"
revocable.revoke()
console.log(proxy.foo) // TypeError が発生
proxy.foo = 1 // TypeError が再び発生
delete proxy.foo // TypeError がここでも発生
typeof proxy // "object" が返され, typeof はどんなトラップも引き起こさない
</pre>
<h2 id="Reflection">リフレクション</h2>
<p>{{jsxref("Reflect")}} は JavaScript で割り込み操作を行うメソッドを提供する組み込みオブジェクトです。そのメソッドは{{jsxref("Global_Objects/Proxy/Proxy","Proxy ハンドラー","","true")}}のメソッドと同じです。</p>
<p><code>Reflect</code> は関数オブジェクトではありません。</p>
<p><code>Reflect</code> はハンドラーから<code><var>ターゲット</var></code>への既定の操作を転送するのに役立ちます。</p>
<p>例えば、{{jsxref("Reflect.has()")}} を使えば、<a href="/ja/docs/Web/JavaScript/Reference/Operators/in"><code>in</code> 演算子</a>を関数として使うことができます。</p>
<pre class="brush: js">Reflect.has(Object, 'assign') // true
</pre>
<h3 id="A_better_apply_function">より優れた <code>apply</code> 関数</h3>
<p>ES5 では、所定の <code>this</code> 値と配列や<a href="/ja/docs/Web/JavaScript/Guide/Indexed_collections#working_with_array-like_objects">配列風オブジェクト</a>として提供される <code>arguments</code> を使って関数を呼び出す {{jsxref("Function.prototype.apply()")}} メソッドがよく使われてきました。</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="Checking_if_property_definition_has_been_successful">プロパティ定義の成否チェック</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)) {
// 成功した時の処理
} else {
// 失敗した時の処理
}</pre>
<p>{{PreviousNext("Web/JavaScript/Guide/Iterators_and_Generators", "Web/JavaScript/Guide/Modules")}}</p>
|