Par spécification, seuls deux types primitifs peuvent servir de clés de propriété d'objet :
type de chaîne, ou
type de symbole.
Sinon, si l'on utilise un autre type, tel qu'un nombre, il est automatiquement converti en chaîne. Donc, obj[1]
est identique à obj["1"]
et obj[true]
est identique à obj["true"]
.
Jusqu'à présent, nous n'utilisions que des chaînes.
Explorons maintenant les symboles, voyons ce qu'ils peuvent faire pour nous.
Un « symbole » représente un identifiant unique.
Une valeur de ce type peut être créée en utilisant Symbol()
:
laissez id = Symbole();
Lors de la création, nous pouvons donner aux symboles une description (également appelée nom de symbole), principalement utile à des fins de débogage :
// id est un symbole avec la description "id" let id = Symbole("id");
Les symboles sont garantis uniques. Même si nous créons de nombreux symboles avec exactement la même description, ce sont des valeurs différentes. La description n'est qu'une étiquette qui n'affecte rien.
Par exemple, voici deux symboles avec la même description – ils ne sont pas égaux :
laissez id1 = Symbole("id"); laissez id2 = Symbole("id"); alerte(id1 == id2); // FAUX
Si vous êtes familier avec Ruby ou un autre langage qui possède également des sortes de « symboles », ne vous y trompez pas. Les symboles JavaScript sont différents.
Donc, pour résumer, un symbole est une « valeur unique primitive » avec une description facultative. Voyons où nous pouvons les utiliser.
Les symboles ne se convertissent pas automatiquement en chaîne
La plupart des valeurs JavaScript prennent en charge la conversion implicite en chaîne. Par exemple, nous pouvons alert
presque n’importe quelle valeur, et cela fonctionnera. Les symboles sont spéciaux. Ils ne se convertissent pas automatiquement.
Par exemple, cette alert
affichera une erreur :
let id = Symbole("id"); alerte(identifiant); // TypeError : impossible de convertir une valeur de symbole en chaîne
C'est une « protection linguistique » contre les erreurs, car les chaînes et les symboles sont fondamentalement différents et ne devraient pas accidentellement se convertir les uns en les autres.
Si nous voulons vraiment afficher un symbole, nous devons appeler explicitement .toString()
dessus, comme ici :
let id = Symbole("id"); alert(id.toString()); // Symbole(id), maintenant ça marche
Ou récupérez la propriété symbol.description
pour afficher uniquement la description :
let id = Symbole("id"); alert(id.description); // identifiant
Les symboles nous permettent de créer des propriétés « cachées » d’un objet, auxquelles aucune autre partie du code ne peut accidentellement accéder ou écraser.
Par exemple, si nous travaillons avec des objets user
appartenant à un code tiers. Nous aimerions leur ajouter des identifiants.
Utilisons pour cela une clé de symbole :
let user = { // appartient à un autre code prénom : "Jean" } ; let id = Symbole("id"); utilisateur[id] = 1 ; alerte( utilisateur[id] ); // nous pouvons accéder aux données en utilisant le symbole comme clé
Quel est l'avantage d'utiliser Symbol("id")
sur une chaîne "id"
?
Comme les objets user
appartiennent à une autre base de code, il est dangereux de leur ajouter des champs, car nous pourrions affecter le comportement prédéfini dans cette autre base de code. Cependant, les symboles ne peuvent pas être accédés accidentellement. Le code tiers ne connaîtra pas les symboles nouvellement définis, il est donc possible d'ajouter des symboles aux objets user
en toute sécurité.
Imaginez également qu'un autre script veuille avoir son propre identifiant dans user
, pour ses propres besoins.
Ensuite, ce script peut créer son propre Symbol("id")
, comme ceci :
//... let id = Symbole("id"); user[id] = "Leur valeur d'identifiant";
Il n'y aura aucun conflit entre nos identifiants et leurs identifiants, car les symboles sont toujours différents, même s'ils portent le même nom.
…Mais si nous utilisions une chaîne "id"
au lieu d'un symbole dans le même but, alors il y aurait un conflit :
let user = { nom : "John" } ; // Notre script utilise la propriété "id" user.id = "Notre valeur d'identifiant"; // ...Un autre script veut également "id" pour ses besoins... user.id = "Leur valeur d'identifiant" // Boum ! écrasé par un autre script !
Si nous voulons utiliser un symbole dans un objet littéral {...}
, nous avons besoin de crochets autour de lui.
Comme ça:
let id = Symbole("id"); laissez l'utilisateur = { nom : "Jean", [id] : 123 // pas "id" : 123 } ;
C'est parce que nous avons besoin de la valeur de la variable id
comme clé, pas de la chaîne « id ».
Les propriétés symboliques ne participent pas à la boucle for..in
.
Par exemple:
let id = Symbole("id"); laissez l'utilisateur = { nom : "Jean", âge : 30 ans, [identifiant] : 123 } ; pour (laisser la clé dans l'utilisateur) alert(key); // nom, âge (pas de symboles) // l'accès direct par le symbole fonctionne alert( "Direct : " + utilisateur[id] ); // Direct : 123
Object.keys(user) les ignore également. Cela fait partie du principe général de « masquage des propriétés symboliques ». Si un autre script ou une bibliothèque boucle sur notre objet, il n'accédera pas de manière inattendue à une propriété symbolique.
En revanche, Object.assign copie les propriétés de chaîne et de symbole :
let id = Symbole("id"); laissez l'utilisateur = { [identifiant] : 123 } ; let clone = Object.assign({}, utilisateur); alert( clone[id] ); // 123
Il n'y a pas de paradoxe ici. C'est intentionnel. L'idée est que lorsque nous clonons un objet ou fusionnons des objets, nous souhaitons généralement que toutes les propriétés soient copiées (y compris les symboles comme id
).
Comme nous l'avons vu, tous les symboles sont généralement différents, même s'ils portent le même nom. Mais parfois, nous voulons que les symboles portant le même nom soient les mêmes entités. Par exemple, différentes parties de notre application souhaitent accéder au symbole "id"
signifiant exactement la même propriété.
Pour y parvenir, il existe un registre mondial des symboles . Nous pouvons y créer des symboles et y accéder plus tard, et cela garantit que les accès répétés du même nom renvoient exactement le même symbole.
Afin de lire (créer en cas d'absence) un symbole du registre, utilisez Symbol.for(key)
.
Cet appel vérifie le registre global, et s'il y a un symbole décrit comme key
, puis le renvoie, sinon crée un nouveau symbole Symbol(key)
et le stocke dans le registre par la key
donnée.
Par exemple:
// lecture depuis le registre global let id = Symbol.for("id"); // si le symbole n'existait pas, il est créé // le relis (peut-être à partir d'une autre partie du code) laissez idAgain = Symbol.for("id"); // le même symbole alert( id === idAgain ); // vrai
Les symboles à l'intérieur du registre sont appelés symboles globaux . Si nous voulons un symbole à l'échelle de l'application, accessible partout dans le code, c'est à cela qu'ils servent.
On dirait Ruby
Dans certains langages de programmation, comme Ruby, il n'y a qu'un seul symbole par nom.
En JavaScript, comme nous pouvons le constater, cela est vrai pour les symboles globaux.
Nous avons vu que pour les symboles globaux, Symbol.for(key)
renvoie un symbole par son nom. Pour faire l'inverse – renvoyer un nom par symbole global – on peut utiliser : Symbol.keyFor(sym)
:
Par exemple:
// récupère le symbole par son nom let sym = Symbol.for("nom"); laissez sym2 = Symbol.for("id"); // récupère le nom par symbole alert( Symbol.keyFor(sym) ); // nom alerte( Symbol.keyFor(sym2) ); // identifiant
Symbol.keyFor
utilise en interne le registre global des symboles pour rechercher la clé du symbole. Cela ne fonctionne donc pas pour les symboles non globaux. Si le symbole n'est pas global, il ne pourra pas le trouver et renvoie undefined
.
Cela dit, tous les symboles ont la propriété description
.
Par exemple:
let globalSymbol = Symbol.for("name"); let localSymbol = Symbol("nom"); alerte( Symbol.keyFor(globalSymbol) ); // nom, symbole global alert( Symbol.keyFor(localSymbol) ); // non défini, pas global alert( localSymbol.description ); // nom
Il existe de nombreux symboles « système » que JavaScript utilise en interne, et nous pouvons les utiliser pour affiner divers aspects de nos objets.
Ils sont répertoriés dans le cahier des charges dans le tableau des symboles connus :
Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.iterator
Symbol.toPrimitive
…et ainsi de suite.
Par exemple, Symbol.toPrimitive
nous permet de décrire la conversion objet en primitive. Nous verrons son utilisation très prochainement.
D’autres symboles deviendront également familiers lorsque nous étudierons les caractéristiques linguistiques correspondantes.
Symbol
est un type primitif pour les identifiants uniques.
Les symboles sont créés avec l'appel Symbol()
avec une description facultative (nom).
Les symboles ont toujours des valeurs différentes, même s'ils portent le même nom. Si nous voulons que les symboles du même nom soient égaux, alors nous devons utiliser le registre global : Symbol.for(key)
renvoie (crée si nécessaire) un symbole global avec key
comme nom. Plusieurs appels de Symbol.for
avec la même key
renvoient exactement le même symbole.
Les symboles ont deux cas d’utilisation principaux :
Propriétés des objets « cachés ».
Si nous voulons ajouter une propriété dans un objet qui « appartient » à un autre script ou à une bibliothèque, nous pouvons créer un symbole et l'utiliser comme clé de propriété. Une propriété symbolique n'apparaît pas dans for..in
, elle ne sera donc pas accidentellement traitée avec d'autres propriétés. De plus, il ne sera pas accessible directement, car un autre script n'a pas notre symbole. Ainsi, la propriété sera protégée contre une utilisation accidentelle ou un écrasement.
Nous pouvons ainsi cacher « secrètement » quelque chose dans des objets dont nous avons besoin, mais que les autres ne devraient pas voir, en utilisant des propriétés symboliques.
Il existe de nombreux symboles système utilisés par JavaScript qui sont accessibles en tant que Symbol.*
. Nous pouvons les utiliser pour modifier certains comportements intégrés. Par exemple, plus loin dans le didacticiel, nous utiliserons Symbol.iterator
pour les itérables, Symbol.toPrimitive
pour configurer la conversion objet en primitive, etc.
Techniquement, les symboles ne sont pas masqués à 100 %. Il existe une méthode intégrée Object.getOwnPropertySymbols(obj) qui nous permet d'obtenir tous les symboles. Il existe également une méthode nommée Reflect.ownKeys(obj) qui renvoie toutes les clés d'un objet, y compris les clés symboliques. Mais la plupart des bibliothèques, fonctions intégrées et constructions syntaxiques n’utilisent pas ces méthodes.