aboutsummaryrefslogtreecommitdiff
path: root/files/fr/web/javascript/guide/meta_programming/index.html
blob: fcec88d12bdfa1c6f8e216c9100e0d9e4553b5f6 (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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
---
title: Métaprogrammation
slug: Web/JavaScript/Guide/Métaprogrammation
tags:
  - Guide
  - JavaScript
  - Proxy
  - Reflect
translation_of: Web/JavaScript/Guide/Meta_programming
---
<div>{{jsSidebar("JavaScript Guide")}} {{PreviousNext("Web/JavaScript/Guide/iterateurs_et_generateurs","Web/JavaScript/Guide/Modules")}}</div>

<p class="summary">À partir d'ECMAScript 2015, JavaScript fournit les objets natifs {{jsxref("Proxy")}} et {{jsxref("Reflect")}}. Ces objets permettent d'intercepter et de définir des comportements spécifiques pour certaines opérations fondamentales du langage (par exemple la recherche d'une propriété, l'affectation, l'énumération, l'appel d'une fonction, etc.). Grâce à ces deux objets, il est possible d'interagir avec le langage lui-même (on parle alors de métaprogrammation).</p>

<h2 id="Les_proxies">Les proxies</h2>

<p>Introduits avec ECMAScript 2015, les objets {{jsxref("Proxy")}} permettent d'intercepter certaines opérations JavaScript et de définir le comportement à avoir quand celles-ci se produisent. Par exemple, on peut intercepter l'accès à une propriété d'un objet :</p>

<pre class="brush: js">var handler = {
  get: function(cible, nom){
    return nom in cible ? cible[nom] : 42;
}};
var p = new Proxy({}, handler);
p.a = 1;
console.log(p.a, p.b); // 1, 42
</pre>

<p>Ici, l'objet <code>Proxy</code> définit une <em>cible</em> (ici c'est un objet vide) et un gestionnaire (<em>handler</em>) qui implémente une <em>trappe</em> pour l'opération <em>get</em>. Ainsi, l'objet qui est « proxyfié » ne renverra pas <code>undefined</code> lorsqu'on tentera d'accéder à une propriété qui n'est pas définie, à la place le nombre 42 sera renvoyé.</p>

<div class="note">
<p>D'autres exemples sont disponibles sur la page de l'objet {{jsxref("Proxy")}}.</p>
</div>

<h3 id="Terminologie">Terminologie</h3>

<p>Lorsqu'on utilise les proxies et leurs fonctionnalités, on utilisera les termes suivants :</p>

<dl>
 <dt>{{jsxref("Objets_globaux/Proxy/handler","gestionnaire (handler)","","true")}}</dt>
 <dd>L'objet qui contient les trappes.</dd>
 <dt>trappes</dt>
 <dd>Les méthodes qui fournissent l'accès aux propriétés. Ce concept est analogue aux trappes utilisées dans les systèmes d'exploitations.</dd>
 <dt>cible</dt>
 <dd>L'objet que le proxy virtualise. C'est généralement un objet utilisé en arrière-plan pour stocker les informations. Les invariants (c'est-à-dire les éléments sémantiques qui doivent rester inchangés) concernant le caractère non-extensible de l'objet ou l'aspect non-configurable des propriétés sont vérifiés par rapport à cet objet cible.</dd>
 <dt>invariants</dt>
 <dd>Les éléments sémantiques qui ne doivent pas être modifiés par les opérations définies dans les proxies. Si un invariant n'est pas respecté au sein d'un gestionnaire, cela provoquera une exception {{jsxref("TypeError")}}.</dd>
</dl>

<h2 id="Les_gestionnaires_et_les_trappes">Les gestionnaires et les trappes</h2>

<p>Le tableau suivant résume les différentes trappes disponibles pour les objets <code>Proxy</code>. Pour plus d'explications et de détails, voir les différents <a href="/fr/docs/Web/JavaScript/Reference/Objets_globaux/Proxy/handler">pages de la référence</a> sur chacun de ces concepts.</p>

<table class="standard-table">
 <thead>
  <tr>
   <th>Gestionnaires / Trappes</th>
   <th>Opérations interceptées</th>
   <th>Invariants</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>{{jsxref("Objets_globaux/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><code>getPrototypeOf</code> doit renvoyer un objet ou <code>null</code>.<br>
    <br>
    Si <code>cible</code> n'est pas extensible, <code>Object.getPrototypeOf(proxy)</code> doit renvoyer le même objet que <code>Object.getPrototypeOf(cible)</code>.</td>
  </tr>
  <tr>
   <td>{{jsxref("Objets_globaux/Proxy/handler/setPrototypeOf", "handler.setPrototypeOf()")}}</td>
   <td>{{jsxref("Object.setPrototypeOf()")}}<br>
    {{jsxref("Reflect.setPrototypeOf()")}}</td>
   <td>
    <p>Si <code>cible</code> n'est pas extensible, le paramètre <code>prototype</code> doit être la même valeur que <code>Object.getPrototypeOf(cible)</code>.</p>
   </td>
  </tr>
  <tr>
   <td>{{jsxref("Objets_globaux/Proxy/handler/isExtensible", "handler.isExtensible()")}}</td>
   <td>
    <p>{{jsxref("Object.isExtensible()")}}</p>

    <p>{{jsxref("Reflect.isExtensible()")}}</p>
   </td>
   <td>
    <p><code>Object.isExtensible(proxy)</code> doit renvoyer la même valeur que <code>Object.isExtensible(target)</code>.</p>
   </td>
  </tr>
  <tr>
   <td>{{jsxref("Objets_globaux/Proxy/handler/preventExtensions", "handler.preventExtensions()")}}</td>
   <td>
    <p>{{jsxref("Object.preventExtensions()")}}</p>

    <p>{{jsxref("Reflect.preventExtensions()")}}</p>
   </td>
   <td>
    <p><code>Object.preventExtensions(proxy)</code> ne renvoie <code>true</code> que si <code>Object.isExtensible(proxy)</code> vaut <code>false</code>.</p>
   </td>
  </tr>
  <tr>
   <td>{{jsxref("Objets_globaux/Proxy/handler/getOwnPropertyDescriptor", "handler.getOwnPropertyDescriptor()")}}</td>
   <td>
    <p>{{jsxref("Object.getOwnPropertyDescriptor()")}}</p>

    <p>{{jsxref("Reflect.getOwnPropertyDescriptor()")}}</p>
   </td>
   <td>
    <p><code>getOwnPropertyDescriptor</code> doit renvoyer un objet ou <code>undefined</code>.</p>

    <p>Une propriété ne peut pas être vue comme non-existante si elle existe comme une propriété propre non-configurable de l'objet cible.</p>

    <p>Une propriété ne peut pas être vue comme non-existante si elle existe comme une propriété propre de la cible et que l'objet cible n'est pas extensible.</p>

    <p>Une propriété ne peut pas être vue comme existante si elle n'existe pas comme une propriété propre de l'objet cible et que l'objet cible n'est pas extensible.</p>

    <p>Une propriété ne peut pas être vue comme non-configurable si elle n'existe pas comme une propriété propre de l'objet cible ou si elle existe comme une propriété configurable propre de l'objet cible.</p>

    <p>Le résultat de <code>Object.getOwnPropertyDescriptor(cible)</code> peut être appliqué à la cible en utilisant <code>Object.defineProperty</code> sans que cela ne lève d'exception.</p>
   </td>
  </tr>
  <tr>
   <td>{{jsxref("Objets_globaux/Proxy/handler/defineProperty", "handler.defineProperty()")}}</td>
   <td>
    <p>{{jsxref("Object.defineProperty()")}}</p>

    <p>{{jsxref("Reflect.defineProperty()")}}</p>
   </td>
   <td>
    <p>Une propriété ne peut pas être ajoutée si l'objet cible n'est pas extensible.</p>

    <p>Une propriété ne peut pas être ajoutée ou être modifiée afin d'être non-configurable si elle n'existe pas comme une propriété propre de l'objet cible et qu'elle n'est pas non-configurable.</p>

    <p>Une propriété peut ne pas être non-configurable si une propriété correspondante configurable existe sur l'objet cible.</p>

    <p>Si une propriété possède une propriété correspondante sur l'objet cible, <code>Object.defineProperty(cible, prop, descripteur)</code> ne doit pas renvoyer d'exception.</p>

    <p>En mode strict, si la valeur de retour de <code>defineProperty</code> est <code>false</code>, cela entraînera une exception {{jsxref("TypeError")}} exception.</p>
   </td>
  </tr>
  <tr>
   <td>{{jsxref("Objets_globaux/Proxy/handler/has", "handler.has()")}}</td>
   <td>
    <p>Requête d'une propriété : <code>toto in proxy</code></p>

    <p>Requête d'une propriété héritée : <code>toto in Object.create(proxy)</code></p>

    <p>{{jsxref("Reflect.has()")}}</p>
   </td>
   <td>
    <p>Une propriété ne peut pas être vue comme non-existante si elle existe comme propriété propre non-configurable de l'objet cible.</p>

    <p>Une propriété ne peut pas être vue comme non-existante si elle existe comme propriété propre de l'objet cible et que l'objet cible n'est pas extensible.</p>
   </td>
  </tr>
  <tr>
   <td>{{jsxref("Objets_globaux/Proxy/handler/get", "handler.get()")}}</td>
   <td>
    <p>Accès à une propriété : <code>proxy[toto]</code> et <code>proxy.truc</code></p>

    <p>Accès à une propriété héritée : <code>Object.create(proxy)[toto]</code></p>

    <p>{{jsxref("Reflect.get()")}}</p>
   </td>
   <td>
    <p>La valeur rapportée pour la propriété doit être la même que la valeur de la propriété correspondante sur l'objet cible si celle-ci est une propriété de donnée non accessible en écriture et non-configurable..</p>

    <p>La valeur rapportée pour une propriété doit être <code>undefined</code> si la propriété correspondante de l'objet cible est une propriété d'accesseur dont l'attribut [[Get]] vaut <code>undefined</code>.</p>
   </td>
  </tr>
  <tr>
   <td>{{jsxref("Objets_globaux/Proxy/handler/set", "handler.set()")}}</td>
   <td>
    <p>Affection d'une propriété : <code>proxy[toto] = truc</code> et <code>proxy.toto = truc</code><br>
     <br>
     Affectation d'une propriété héritée : <code>Object.create(proxy)[toto] = truc</code><br>
     <br>
     {{jsxref("Reflect.set()")}}</p>
   </td>
   <td>
    <p>Il est impossible de modifier la valeur d'une propriété pour que celle-ci soit différente de la valeur de la propriété correspondante de l'objet cible si la propriété de l'objet cible est une propriété de donnée qui n'est pas accessible en écriture et qui n'est pas configurable.</p>

    <p>Il est impossible de modifier la valeur d'une propriété si la propriété correspondante de l'objet cible est une propriété d'accesseur non-configurable dont l'attribut [[Set]] vaut <code>undefined</code>.</p>

    <p>En mode strict, si le gestionnaire pour <code>set</code> renvoie <code>false</code>, cela provoquera une exception {{jsxref("TypeError")}}.</p>
   </td>
  </tr>
  <tr>
   <td>{{jsxref("Objets_globaux/Proxy/handler/deleteProperty", "handler.deleteProperty()")}}</td>
   <td>
    <p>Suppression d'une propriété : <code>delete proxy[toto]</code> et <code>delete proxy.toto</code><br>
     <br>
     {{jsxref("Reflect.deleteProperty()")}}</p>
   </td>
   <td>Une propriété ne peut pas être supprimée si elle existe comme une propriété propre non-configurable de l'objet cible.</td>
  </tr>
  <tr>
   <td>{{jsxref("Objets_globaux/Proxy/handler/enumerate", "handler.enumerate()")}}</td>
   <td>
    <p>Lister les propriétés avec <code>for...in</code> : <code>for (var nom in proxy) {...}</code><br>
     <br>
     {{jsxref("Reflect.enumerate()")}}</p>
   </td>
   <td>La méthode <code>enumerate</code> doit renvoyer un objet.</td>
  </tr>
  <tr>
   <td>{{jsxref("Objets_globaux/Proxy/handler/ownKeys", "handler.ownKeys()")}}</td>
   <td>
    <p>{{jsxref("Object.getOwnPropertyNames()")}}<br>
     {{jsxref("Object.getOwnPropertySymbols()")}}<br>
     {{jsxref("Object.keys()")}}<br>
     {{jsxref("Reflect.ownKeys()")}}</p>
   </td>
   <td>
    <p>Le résultat de <code>ownKeys</code> est une liste.<br>
     <br>
     Le type de chaque élément de la liste est soit une {{jsxref("String")}} soit un  {{jsxref("Symbol")}}.<br>
     <br>
     La liste résultatnte doit contenir les clés de toutes les propriétés non-configurables de l'objet cible.<br>
     <br>
     Si l'objet cible n'est pas extensible, la liste résultante doit contenir toutes les clés des propriétés propres de l'objet cibles et aucune autre valeur.</p>
   </td>
  </tr>
  <tr>
   <td>{{jsxref("Objets_globaux/Proxy/handler/apply", "handler.apply()")}}</td>
   <td>
    <p><code>proxy(..args)</code><br>
     <br>
     {{jsxref("Function.prototype.apply()")}} and {{jsxref("Function.prototype.call()")}}<br>
     <br>
     {{jsxref("Reflect.apply()")}}</p>
   </td>
   <td>Il n'y a pas d'invariant pour la méthode <code>handler.apply</code>.</td>
  </tr>
  <tr>
   <td>{{jsxref("Objets_globaux/Proxy/handler/construct", "handler.construct()")}}</td>
   <td>
    <p><code>new proxy(...args)</code><br>
     {{jsxref("Reflect.construct()")}}</p>
   </td>
   <td>Le résultat doit être un <code>Objet</code>.</td>
  </tr>
 </tbody>
</table>

<h2 id="Proxies_révocables">Proxies révocables</h2>

<p>La méthode {{jsxref("Proxy.revocable()")}} est utilisée pour créer un objet <code>Proxy</code> qui puisse être révoqué. Cela signifie que que le proxy pourra être révoqué avec la fonction <code>revoke</code> et arrêtera le proxy. Après cet arrêt, toute opération sur le proxy entraînera une exception {{jsxref("TypeError")}}.</p>

<pre class="brush: js">var revocable = Proxy.revocable({}, {
  get: function(cible, nom) {
    return "[[" + nom + "]]";
  }
});
var proxy = revocable.proxy;
console.log(proxy.toto); // "[[toto]]"

revocable.revoke();

console.log(proxy.toto); // déclenche une TypeError
proxy.toto = 1;          // une TypeError encore
delete proxy.toto;       // toujours une TypeError
typeof proxy             // "object", typeof ne déclenche aucune trappe</pre>

<h2 id="Réflexion">Réflexion</h2>

<p>{{jsxref("Reflect")}} est un objet natif qui fournit des méthodes pour les opérations JavaScript qui peuvent être interceptées. Ces méthodes sont les mêmes que celles gérées par les {{jsxref("Objets_globaux/Proxy/handler","gestionnaires de proxy","","true")}}. <code>Reflect</code> n'est pas un constructeur et ne peut pas être utilisé comme une fonction !</p>

<p><code>Reflect</code> aide à transférer les opérations par défaut depuis le gestionnaire vers la cible.</p>

<p>Par exemple, avec {{jsxref("Reflect.has()")}}, on obtient le comportement de l'opérateur <a href="/fr/docs/Web/JavaScript/Reference/Opérateurs/L_opérateur_in"><code>in</code></a> sous forme d'une fonction :</p>

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

<p>{{PreviousNext("Web/JavaScript/Guide/iterateurs_et_generateurs","Web/JavaScript/Guide/Modules")}}</p>