|
| 1 | +# Propriété opérateurs de lecture et d'écriture |
| 2 | + |
| 3 | +Il existe deux types de propriétés d'objet. |
| 4 | + |
| 5 | +Le premier désigne *les propriétés de données*. Nous savons déjà comment les utiliser. Toutes les propriétés que nous avons utilisées jusqu'à maintenant étaient des propriétés de données. |
| 6 | + |
| 7 | +Le second type de propriétés est quelque chose de nouveau. Ce sont les *propriétés des accesseurs*. Ce sont essentiellement des fonctions qui s'exécutent lors de la lecture d'une valeur ou son écriture, mais elles ressemblent à des propriétés classiques pour un code externe. |
| 8 | + |
| 9 | +## Les opérateurs de lecture et d'écriture |
| 10 | + |
| 11 | +Les propriétés des accesseurs sont représentées par les méthodes "lecture" et "écriture". Dans un objet litéral ils sont notés comme`get` et `set`: |
| 12 | + |
| 13 | +```js |
| 14 | +let obj = { |
| 15 | + *!*get propName()*/!* { |
| 16 | + // opérateur de lecture, code exécuté en accédant à obj.propName |
| 17 | + }, |
| 18 | + |
| 19 | + *!*set propName(value)*/!* { |
| 20 | + // opérateur d'écriture, code exécuté en assignant obj.propName = value |
| 21 | + } |
| 22 | +}; |
| 23 | +``` |
| 24 | + |
| 25 | +L'opérateur de lecture fonctionne lors de la lecture de `obj.propName`, l'opérateur d'écriture -- lors de son affectation. |
| 26 | + |
| 27 | +Par exemple, nous avons un objet `user` avec `name` et `surname`: |
| 28 | + |
| 29 | +```js |
| 30 | +let user = { |
| 31 | + name: "John", |
| 32 | + surname: "Smith" |
| 33 | +}; |
| 34 | +``` |
| 35 | + |
| 36 | +Maintenant nous voulons ajouter une propriété `fullName`, qui serait`"John Smith"`. Bien sûr, nous ne voulons pas copier-coller l'information existante, aussi nous pouvons la créer comme accesseur: |
| 37 | + |
| 38 | +```js run |
| 39 | +let user = { |
| 40 | + name: "John", |
| 41 | + surname: "Smith", |
| 42 | + |
| 43 | +*!* |
| 44 | + get fullName() { |
| 45 | + return `${this.name} ${this.surname}`; |
| 46 | + } |
| 47 | +*/!* |
| 48 | +}; |
| 49 | + |
| 50 | +*!* |
| 51 | +alert(user.fullName); // John Smith |
| 52 | +*/!* |
| 53 | +``` |
| 54 | + |
| 55 | +De l'extérieur, une propriété d'accesseur ressemble à une propriété classique. C'est l'idée avec les propriétés des accesseurs. Nous n'utilisons pas *call* pour appeler `user.fullName` comme une fonction, nous la lisons *read* normalement: l'accesseur s'exécute en coulisses. |
| 56 | + |
| 57 | +A partir de maintenant, `fullName` a juste un accesseur. Si nous tentons d'affecter `user.fullName=`, cela provoquera une erreur: |
| 58 | + |
| 59 | +```js run |
| 60 | +let user = { |
| 61 | + get fullName() { |
| 62 | + return `...`; |
| 63 | + } |
| 64 | +}; |
| 65 | + |
| 66 | +*!* |
| 67 | +user.fullName = "Test"; // Error (property has only a getter) |
| 68 | +*/!* |
| 69 | +``` |
| 70 | + |
| 71 | +Corrigeons en ajoutant un opérateur d'écriture pour `user.fullName`: |
| 72 | + |
| 73 | +```js run |
| 74 | +let user = { |
| 75 | + name: "John", |
| 76 | + surname: "Smith", |
| 77 | + |
| 78 | + get fullName() { |
| 79 | + return `${this.name} ${this.surname}`; |
| 80 | + }, |
| 81 | + |
| 82 | +*!* |
| 83 | + set fullName(value) { |
| 84 | + [this.name, this.surname] = value.split(" "); |
| 85 | + } |
| 86 | +*/!* |
| 87 | +}; |
| 88 | + |
| 89 | +// set fullName est exécuté avec la valeur donnée. |
| 90 | +user.fullName = "Alice Cooper"; |
| 91 | + |
| 92 | +alert(user.name); // Alice |
| 93 | +alert(user.surname); // Cooper |
| 94 | +``` |
| 95 | + |
| 96 | +Le résultat obtenu est une propriété "virtuelle" `fullName`. Elle est lisible et inscriptible. |
| 97 | + |
| 98 | +## Descripteurs de l'accesseur |
| 99 | + |
| 100 | +Les descripteurs pour les propriétés de l'accesseur sont différents de ceux concernant les propriétés de données. |
| 101 | + |
| 102 | +Pour les propriétés de l'accesseur, il n'y a pas de valeur `value` ou inscriptible `writable`, mais à la place il y a les fonctions `get` et `set`. |
| 103 | + |
| 104 | +Ainsi, un descripteur d'accesseur peut avoir: |
| 105 | + |
| 106 | +- **`get`** -- une fonction sans arguments, qui s'exécute quand une propriété est lue, |
| 107 | +- **`set`** -- une fonction avec un argument, qui est appelée quand la propriété est écrite, |
| 108 | +- **`enumerable`** -- identique aux propriétés de données, |
| 109 | +- **`configurable`** -- identique aux propriétés de données. |
| 110 | + |
| 111 | +Par exemple, pour créer un accesseur `fullName` avec `defineProperty`, nous pouvons passer un descripteur avec `get` et `set`: |
| 112 | + |
| 113 | +```js run |
| 114 | +let user = { |
| 115 | + name: "John", |
| 116 | + surname: "Smith" |
| 117 | +}; |
| 118 | + |
| 119 | +*!* |
| 120 | +Object.defineProperty(user, 'fullName', { |
| 121 | + get() { |
| 122 | + return `${this.name} ${this.surname}`; |
| 123 | + }, |
| 124 | + |
| 125 | + set(value) { |
| 126 | + [this.name, this.surname] = value.split(" "); |
| 127 | + } |
| 128 | +*/!* |
| 129 | +}); |
| 130 | + |
| 131 | +alert(user.fullName); // John Smith |
| 132 | + |
| 133 | +for(let key in user) alert(key); // prénom, nom |
| 134 | +``` |
| 135 | + |
| 136 | +Veuillez noter qu'une propriété peut être soit un accesseur (a les méthodes `get/set`) soit une propriété de donnée (a une valeur `value`), pas les deux ensemble. |
| 137 | + |
| 138 | +Si nous essayons d'utiliser à la fois `get` et `value` dans le même descripteur, cela produira une erreur: |
| 139 | + |
| 140 | +```js run |
| 141 | +*!* |
| 142 | +// Error: Invalid property descriptor. |
| 143 | +*/!* |
| 144 | +Object.defineProperty({}, 'prop', { |
| 145 | + get() { |
| 146 | + return 1 |
| 147 | + }, |
| 148 | + |
| 149 | + value: 2 |
| 150 | +}); |
| 151 | +``` |
| 152 | + |
| 153 | +## Des opérateurs de lecture/écriture plus intelligents |
| 154 | + |
| 155 | +Les opérateurs de lecture/écriture peuvent être utilisés comme des fonctions wrapper (pour appeler une ou d'autres fonctions) à la place de valeurs "réelles" d'une propriété en vue d'augmenter le contrôle des opérations. |
| 156 | + |
| 157 | +Par exemple, si nous voulons interdire les noms trop courts pour `user`, nous pouvons avoir un opérateur d'écriture `name` et garder la valeur dans une propriété séparée `_name`: |
| 158 | + |
| 159 | +```js run |
| 160 | +let user = { |
| 161 | + get name() { |
| 162 | + return this._name; |
| 163 | + }, |
| 164 | + |
| 165 | + set name(value) { |
| 166 | + if (value.length < 4) { |
| 167 | + alert("Name is too short, need at least 4 characters"); |
| 168 | + return; |
| 169 | + } |
| 170 | + this._name = value; |
| 171 | + } |
| 172 | +}; |
| 173 | + |
| 174 | +user.name = "Pete"; |
| 175 | +alert(user.name); // Pete |
| 176 | + |
| 177 | +user.name = ""; // Le nom est trop court... |
| 178 | +``` |
| 179 | + |
| 180 | +Ainsi, le nom est stocké dans la propriété `_name`, et l'accès s'effectue via les opérateurs de lecture et d'écriture. |
| 181 | + |
| 182 | +Techniquement, le code externe est capable d'accéder au nom directement en utilisant `user._name`. Mais d'après une convention largement reconnue, les propriétés commençant avec un underscore `"_"` sont internes et ne devraient pas être accessibles hors de l'objet. |
| 183 | + |
| 184 | + |
| 185 | +## Utilisation pour la compatibilité |
| 186 | + |
| 187 | +Une des utilisations géniales des accesseurs est qu'ils permettent de prendre le contrôle d'une propriété de données "classique" à tout moment en la remplaçant par un opérateur de lecture et d'écriture, et de modifier son comportement. |
| 188 | + |
| 189 | +Imaginez que nous commencions à mettre en oeuvre des objets utilisateurs utilisant les propriétés de données `name` et `age`: |
| 190 | + |
| 191 | +```js |
| 192 | +function User(name, age) { |
| 193 | + this.name = name; |
| 194 | + this.age = age; |
| 195 | +} |
| 196 | + |
| 197 | +let john = new User("John", 25); |
| 198 | + |
| 199 | +alert( john.age ); // 25 |
| 200 | +``` |
| 201 | + |
| 202 | +...Mais tôt ou tard, les choses peuvent changer. Au lieu de `age` nous pouvons décider de stocker `birthday`, parce que c'est plus précis et commode: |
| 203 | + |
| 204 | +```js |
| 205 | +function User(name, birthday) { |
| 206 | + this.name = name; |
| 207 | + this.birthday = birthday; |
| 208 | +} |
| 209 | + |
| 210 | +let john = new User("John", new Date(1992, 6, 1)); |
| 211 | +``` |
| 212 | + |
| 213 | +Maintenant, que faire avec l'ancien code qui utilise encore la propriété `age`? |
| 214 | + |
| 215 | +Nous pouvons essayer de trouver toutes les occurences et les corriger mais cela prend du temps et peut être compliqué si ce code est utilisé par beaucoup d'autres personnes. Et en plus, `age` est une bonne chose à avoir dans `user`, n'est-ce pas? |
| 216 | + |
| 217 | +Gardons-le. |
| 218 | + |
| 219 | +L'ajout d'un opérateur de lecture pour `age` résoud le problème: |
| 220 | + |
| 221 | +```js run no-beautify |
| 222 | +function User(name, birthday) { |
| 223 | + this.name = name; |
| 224 | + this.birthday = birthday; |
| 225 | + |
| 226 | +*!* |
| 227 | + // l'âge est calculé à partir de la date actuelle et de l'anniversaire |
| 228 | + Object.defineProperty(this, "age", { |
| 229 | + get() { |
| 230 | + let todayYear = new Date().getFullYear(); |
| 231 | + return todayYear - this.birthday.getFullYear(); |
| 232 | + } |
| 233 | + }); |
| 234 | +*/!* |
| 235 | +} |
| 236 | + |
| 237 | +let john = new User("John", new Date(1992, 6, 1)); |
| 238 | + |
| 239 | +alert( john.birthday ); // anniversaire est disponible |
| 240 | +alert( john.age ); // ...ainsi que l'âge |
| 241 | +``` |
| 242 | + |
| 243 | +Maintenant l'ancien code fonctionne aussi et nous avons une chouette propriété supplémentaire. |
| 244 | + |
0 commit comments