ES6 : les symboles

image_pdfimage_print

Avec ES5 on a 5 primitives : chaîne de caractères (string), nombre (number), booléen (boolean), null et undefined. ES6 introduit une nouvelle primitive : symbole (symbol). Son but principal est de pouvoir créer une propriété d’objet avec une référence unique qui ne puisse pas entrer en conflit avec une autre référence, ce qui est délicat à réaliser avec le type string.

La syntaxe

La syntaxe est simple :

let monSymbole = Symbol();

Avec cet appel à Symbol je crée un symbole unique qui n’est égal à aucune autre valeur ou symbole.

Je peux ensuite utiliser ce symbole comme clé d’une propriété d’objet :

let objet = {};
objet[monSymbole] = "C'est moi !";
console.log(objet[monSymbole]);  // C'est moi !

Là je suis tranquille parce que ma propriété ne pourra pas entrer en conflit avec une autre du même nom.

On peut assigner une description au symbole :

let prenom = Symbol("Le prénom");
let identite = {};
identite[prenom] = "Alfred";
console.log(identite[prenom]); // Alfred
console.log(prenom.toString()); // Symbol(Le prenom)

La description sert uniquement pour le débogage.

Pour vérifier qu’on a bien quelque chose d’unique :

console.log(Symbol('machin') === Symbol('machin'));  // false

Énumérer les propriétés

Avec ES5 on peut énumérer les pro‌priétés d’un objet avec la méthode getOwnPropertyNames. Mais cette méthode ne retourne pas les symboles. Avec ES6 on dispose de la méthode getOwnPropertySymbols :

let prenom = Symbol('Le prenom'); 
let identite = {
    [prenom]: 'Alfred',
  	'nom': 'Dupont'
};
console.log(identite[prenom]); // Alfred
console.log(identite.nom);  // Dupont

let names = Object.getOwnPropertyNames(identite);
console.log(names.length);  // 1
console.log(names[0]);  // nom

let symbols = Object.getOwnPropertySymbols(identite);
console.log(symbols.length); // 1
console.log(symbols[0].toString()); // Symbol(Le prenom)
console.log(identite[symbols[0]]); // Alfred

Ici on retrouve bien la propriété nommée et celle qui a comme clé un symbole.

Les conversions

Si vous tentez de convertir de façon implicite un symbole en chaîne de caractères vous aller tomber sur une erreur :

let monSymbole = Symbol();
console.log('Mon symbole : '+ monSymbole); // Erreur !
console.log(`Mon symbole : ${monSymbole}`); // Erreur !

Il faut le faire de façon explicite :

let monSymbole = Symbol();

console.log('Mon symbole : '+ String(monSymbole)); // Symbol()
console.log(`Mon symbole : ${String(monSymbole)}`); // Symbol()

Par contre une conversion en booléen va fonctionner et renvoyer true pour un symbole.

Une conversion en nombre est impossible et génère une erreur.

Les symboles bien connus (well-known symbols)

Il n’y a pas que les symboles que vous créez, JavaScript possède en interne un certain nombre de symboles pour des actions qui n’étaient pas exposées avec ES5. Comme il y en a beaucoup je ne vais pas tous les détailler ici, vous pouvez trouver une liste exhaustive par exemple sur cette page.

Je vais en décrire deux dans cet article pour comprendre le principe.

Symbol.hasInstance

Ce symbole sert à savoir si le constructeur d’un objet reconnaît un objet spécifique comme l’une de ses instances :

console.log(Array[Symbol.hasInstance]([]));  // true

ES6 redéfinit la méthode instanceof pour appeler cette méthode. Donc le code précédent est équivalent à celui-ci :

console.log([] instanceof Array);  // true

Là où ça devient intéressant c’est qu’on peut modifier le traitement effectué par instanceof. Voici un exemple sommaire :

function GrandNombre() {}
Object.defineProperty(GrandNombre, Symbol.hasInstance, {
  value: function(valeur) {
    return (valeur > 1000);
  }
});
let nombre1 = 8;
let nombre2 = 2000;
console.log(nombre1 instanceof GrandNombre); // false
console.log(nombre2 instanceof GrandNombre); // true

On surcharge la méthode Symbol.hasInstance pour définir un nouveau comportement.

Symbol.toPrimitive

JavaScript doit fréquemment transformer un objet en primitive selon le contexte. Par exemple lorsque vous comparez deux éléments ou tout simplement lorsque vous voulez afficher un résultat. JavaScript fait cela automatiquement mais avec la méthode Symbol.toPrimitive vous pouvez définir précisément pour un objet particulier ce comportement :

function Prix(montant) {
  this.montant = montant;
}
Prix.prototype[Symbol.toPrimitive] = function(hint) {
    switch (hint) {
        case "string":
            return this.montant + ' €';
        case "number":
            return this.montant;
        case "default":
         	return this.montant + " Euros";
    }
};
var prix = new Prix(500);
console.log('Voici le prix : ' + prix); // Voici le prix : 500 Euros
console.log(prix * 2); // 1000
console.log(String(prix)); // 500 €

On surcharge la méthode Symbol.toPrimitive de l’objet Prix. On renvoie une valeur adaptée selon que l’argument est considéré comme une chaîne de caractères, un nombre, ou autre chose (valeur par défaut).

Je pense qu’avec ces deux exemples vous avez compris le principe et le côté bien pratique de ces nouvelles possibilités !

En résumé

  • ES6 introduit une nouvelle primitive : symbol.
  • Un symbole est une clé unique qui évite les conflits.
  • On peut ajouter une description à un symbole pour faciliter le débogage.
  • On doit convertir de façon explicite un symbole en autre primitive.
  • Les symboles bien-connus (well-known symbols) sont des symboles faisant partie de JavaScript qui permettent de modifier certains comportements.

Laisser un commentaire