Aller au contenu principal
JavaScript (ECMAScript 2024) Standard : Unicode 17.0 / ECMA-262 Time: 8 min de lecture Révisé : septembre 2024

Chaînes Unicode en JavaScript

Maîtrisez Unicode en JavaScript : paires de substitution UTF-16, String.prototype.codePointAt, séquences d'échappement ES2024 et Intl.Segmenter.

TL;DR — Réponse directe

Réponse technique directe

Les chaînes JavaScript sont des séquences d'unités de code UTF-16 de 16 bits, et non des caractères Unicode individuels ou des symboles perçus par l'utilisateur. Les caractères supplémentaires hors du Basic Multilingual Plane (comme les emojis et les idéogrammes rares) nécessitent deux unités de code UTF-16 (une paire de substitution), tandis que les graphèmes composés nécessitent plusieurs points de code.

💡
Règle de production : N'utilisez jamais string.length ni string[i] pour mesurer ou découper du texte visible par l'utilisateur. Utilisez String.prototype.codePointAt() pour les points de code complets, et Intl.Segmenter pour les clusters de graphèmes perçus par l'utilisateur.

La différence entre unités de code UTF-16, points de code Unicode et clusters de graphèmes en JavaScript

JAVASCRIPT unicode-distinction.js
const emoji = '😀';

// 1. String.prototype.length counts UTF-16 code units
console.log(emoji.length); // 2

// 2. String iterator ([...str]) counts Unicode code points
console.log([...emoji].length); // 1

// 3. Composite emoji with Zero-Width Joiner (ZWJ)
const family = '👨‍👦';
console.log(family.length); // 5 code units
console.log([...family].length); // 3 code points

// 4. Intl.Segmenter counts user-perceived grapheme clusters
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
console.log([...segmenter.segment(family)].length); // 1 user character
Sortie du programme
2
1
5
3
1

Pourquoi cela arrive : le modèle de chaîne d'ECMAScript

Pour comprendre le comportement des chaînes JavaScript, les développeurs doivent maîtriser les trois couches distinctes de représentation Unicode dans le navigateur et dans Node.js.

1. L'héritage historique d'UTF-16

Quand Brendan Eich a conçu JavaScript en 1995, Unicode était défini comme un encodage à largeur fixe de 16 bits (UCS-2) contenant au maximum 65 536 caractères (le Basic Multilingual Plane, BMP). En 1996, Unicode s'est étendu à 1 114 112 points de code (plans 0 à 16). Plutôt que de casser les API existantes, ECMAScript a adopté UTF-16. En UTF-16, les caractères au-dessus de U+FFFF sont représentés par deux unités de code de 16 bits appelées paire de substitution : un substitut élevé (\uD800..\uDBFF) suivi d'un substitut faible (\uDC00..\uDFFF).

2. Unités de code vs points de code

Un point de code est la valeur numérique abstraite (U+0000..U+10FFFF). L'emoji visage souriant 😀 est le point de code U+1F600. En mémoire JavaScript, U+1F600 est encodé comme les deux unités de code UTF-16 0xD83D et 0xDE00. Des méthodes comme .charCodeAt(0) et la propriété .length n'inspectent que des unités individuelles de 16 bits, voyant deux unités au lieu d'un caractère.

3. Points de code vs clusters de graphèmes perçus par l'utilisateur

Même l'itération par point de code via Array.from() ou for...of est insuffisante pour le texte moderne. Les séquences d'emoji composées (comme 👨‍👦) se composent de plusieurs points de code : homme (U+1F468) + ZWJ (U+200D) + garçon (U+1F466). De même, les signes combinants comme « e » (U+0065) + accent aigu combinant (U+0301) forment deux points de code rendus comme un seul glyphe visible « é ». Seul Intl.Segmenter, conforme à Unicode UAX #29, mesure avec précision les caractères visuels.

4. Substituts isolés et chaînes mal formées

Comme les chaînes JavaScript sont des séquences d'entiers arbitraires de 16 bits, elles peuvent contenir des substituts isolés (p. ex. "\uD83D" sans substitut faible accompagnateur). ECMAScript autorise les substituts isolés en mémoire, mais les API exigeant de l'UTF-8 bien formé (comme TextEncoder, fetch() ou WebSockets) remplaceront les substituts isolés par U+FFFD (Replacement Character) ou lèveront une erreur.

Modèle d'exécution du langage et du runtime

Langage cible JavaScript
Standard moderne ECMAScript 2024
Version minimale ES2015 (ES6)
Prise en charge du segmenteur Chrome 87+, Safari 14.1+, Firefox 125+, Node.js 16.0+, Bun 1.0+, Deno 1.8+
Optimisation du moteur

V8 utilise une optimisation de représentation interne : les chaînes ne contenant que des caractères Latin-1 (U+0000..U+00FF) sont stockées comme des tableaux de 1 octet (SeqOneByteString). Les chaînes contenant un caractère au-dessus d'U+00FF sont promues en tableaux UTF-16 de 2 octets (SeqTwoByteString).

Erreurs courantes vs modèles de production

Découvrez quels modèles échouent en production et leurs alternatives modernes conformes aux standards.

Compter les caractères visibles par l'utilisateur

Compteur de caractères dans les formulaires d'interface, limites SMS, champs de biographie.
À ÉVITER : ❌ String.prototype.length
const str = '👨‍💻';
console.log(str.length);
// Returns 5 (UTF-16 code units)
Pourquoi cela échoue : String.length compte des unités de code de 16 bits. Les emojis avec des joiners de largeur nulle et des paires de substitution rapportent des décomptes gonflés.
RECOMMANDÉ : ✅ Intl.Segmenter (UAX #29)
const str = '👨‍💻';
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
console.log([...segmenter.segment(str)].length);
// Returns 1 (true user-perceived character)
Pourquoi c'est mieux : Implémente les limites de clusters de graphèmes étendus d'Unicode UAX #29, liant correctement les signes combinants, les modificateurs et les séquences ZWJ.

Itérer sur les caractères d'une chaîne

Analyse de jetons, construction de transformateurs de chaînes personnalisés.
À ÉVITER : ❌ Boucle par indice ou str.split("")
const text = '🔥hello';
for (let i = 0; i < text.length; i++) {
  console.log(text[i]); // splits 🔥 into \uD83D and \uDD25!
}
const chars = text.split(''); // ['\uD83D', '\uDD25', 'h', 'e', 'l', 'l', 'o']
Pourquoi cela échoue : L'accès direct par crochets text[i] et split("") opèrent sur les unités de code, déchirant les paires de substitution en deux et produisant des substituts isolés invalides.
RECOMMANDÉ : ✅ for...of ou Array.from()
const text = '🔥hello';
for (const char of text) {
  console.log(char); // '🔥', 'h', 'e', 'l', 'l', 'o'
}
const chars = [...text]; // ['🔥', 'h', 'e', 'l', 'l', 'o']
Pourquoi c'est mieux : L'itérateur de chaîne ECMAScript est conscient des points de code et avance automatiquement au-delà des paires de substitution élevé/faible prises ensemble.

Lire des points de code numériques

Convertisseurs d'encodage de caractères, hachage cryptographique, métriques de polices.
À ÉVITER : ❌ charCodeAt(i)
const char = '😀';
console.log(char.charCodeAt(0).toString(16));
// Returns 'd83d' (High surrogate only!)
Pourquoi cela échoue : charCodeAt ne renvoie que le nombre de 16 bits à ce décalage. Pour les caractères supplémentaires, il ne donne que la moitié de la paire de substitution.
RECOMMANDÉ : ✅ codePointAt(i)
const char = '😀';
console.log(char.codePointAt(0).toString(16));
// Returns '1f600' (True Unicode code point!)
Pourquoi c'est mieux : codePointAt inspecte l'unité de code courante et la suivante ; si elles forment une paire de substitution valide, il renvoie la valeur scalaire de 21 bits.

Écrire des séquences d'échappement Unicode

Littéraux de code source, charges utiles JSON, définitions d'expressions régulières.
À ÉVITER : ❌ Séquences d'échappement historiques par paire de substitution
const fire = '\uD83D\uDD25'; // 🔥
// Prone to transposition, hard to read, manual math needed
Pourquoi cela échoue : Oblige les développeurs à calculer manuellement les décalages hexadécimaux des substituts élevés et faibles.
RECOMMANDÉ : ✅ Séquences d'échappement Unicode à accolades ES6
const fire = '\u{1F525}'; // 🔥
const smile = '\u{1F600}'; // 😀
Pourquoi c'est mieux : Accepte directement tout point de code Unicode valide de \u{0} à \u{10FFFF} sans calcul de substitution.

Matrice des cas limites et invariants

Une matrice de tests diversifiée comparant le comportement des caractères entre ASCII, signes combinants, emojis, drapeaux et fragments de substitution.

Cas de test Glyphe d'entrée Unités de code Points de code Graphèmes Catégorie Explication technique
Lettre latine ASCII A 1 1 1 Basic Latin U+0041 tient dans un seul octet de 7 bits, une unité de code de 16 bits et un point de code.
Accent précomposé é 1 1 1 Latin-1 Supplement U+00E9 accent aigu précomposé dans le BMP. 1 unité de code, 1 point de code, 1 graphème.
Accent décomposé 2 2 1 Combining Mark Lettre de base e (U+0065) + accent aigu combinant (U+0301). 2 points de code rendus comme 1 graphème visuel.
Symbole monétaire 1 1 1 Currency Signe euro U+20AC situé dans le BMP. 1 unité de code, 1 point de code, 1 graphème.
Emoji standard 😀 2 1 1 Supplementary Plane Visage souriant U+1F600 dans le plan 1. Encodé comme 2 unités de code UTF-16 (D83D DE00).
Séquence d'emoji avec ZWJ 👨‍👦 5 3 1 Emoji Sequence Homme (U+1F468) + ZWJ (U+200D) + garçon (U+1F466). 3 points de code joints en 1 glyphe de famille.
Drapeau arc-en-ciel 🏳️‍🌈 6 4 1 Emoji Sequence Drapeau blanc (U+1F3F3) + sélecteur de variante 16 (U+FE0F) + ZWJ (U+200D) + arc-en-ciel (U+1F308).
Drapeau national (indicateurs régionaux) 🇺🇳 4 2 1 Flag Sequence Indicateur régional U (U+1F1FA) + indicateur régional N (U+1F1F3). 2 points de code, 1 graphème.
Substitut élevé isolé 1 1 1 Surrogate Fragment Substitut élevé isolé 0xD83D. Valide en mémoire ECMAScript, mais mal formé en UTF-8.

Core API Reference

Standard library methods, runtime signatures, and Unicode semantics.

str.codePointAt(index: number): number | undefined

Renvoie la valeur du point de code Unicode (entier) à l'index d'unité de code UTF-16 spécifié.

Sémantique Unicode : Si l'index pointe vers le début d'une paire de substitution valide, renvoie le point de code complet de 21 bits (U+0000..U+10FFFF). S'il pointe vers un substitut faible ou un substitut solitaire, renvoie uniquement la valeur de l'unité de code de substitution.
⚠️ Mises en garde importantes : Le paramètre d'index reste un décalage en unités de code UTF-16, et non en points de code. Pour avancer caractère par caractère, avancez de 2 si codePoint >= 0x10000.
String.fromCodePoint(...codePoints: number[]): string

Crée une chaîne à partir d'une séquence de points de code Unicode de 21 bits.

Sémantique Unicode : Calcule et émet automatiquement les paires de substitution élevé/faible pour les points de code >= 0x10000.
⚠️ Mises en garde importantes : Lève une RangeError si un point de code est négatif, supérieur à 0x10FFFF ou NaN.
new Intl.Segmenter(locales?: string | string[], options?: { granularity: "grapheme" | "word" | "sentence" })

Fournit une segmentation de texte sensible à la locale, permettant une itération fiable des clusters de graphèmes perçus par l'utilisateur.

Sémantique Unicode : Conforme aux algorithmes de segmentation de texte par défaut de l'Unicode Standard Annex #29 (UAX #29).
⚠️ Mises en garde importantes : Disponible dans Chrome 87+, Safari 14.1+, Firefox 125+, Node.js 16.0+. Utilisez un polyfill pour les navigateurs anciens.
str.normalize(form?: "NFC" | "NFD" | "NFKC" | "NFKD"): string

Renvoie la forme de normalisation Unicode de la chaîne appelante.

Sémantique Unicode : Conforme à l'Unicode Standard Annex #15 (UAX #15). NFC (par défaut) : décomposition canonique suivie d'une composition canonique.
⚠️ Mises en garde importantes : Normalisez toujours les entrées utilisateur en NFC avant de comparer des noms d'utilisateur ou de stocker du texte dans des bases de données.
new RegExp(pattern, "v")

Indicateur d'expression régulière UnicodeSets d'ECMAScript 2024 activant les opérations d'ensembles et les propriétés de chaîne.

Sémantique Unicode : Remplace l'indicateur /u. Prend en charge \p{Extended_Pictographic} et les ensembles de chaînes multi-caractères comme [\p{White_Space}--[\n\r]].
⚠️ Mises en garde importantes : Pris en charge dans Chrome 112+, Safari 17+, Firefox 116+, Node.js 20+.
Outil interactif

Inspecteur interactif de chaînes et d'Unicode en JavaScript

Testez n'importe quelle chaîne ou séquence d'emojis en temps réel pour inspecter ses unités de code UTF-16, ses points de code Unicode et ses clusters de graphèmes dans votre navigateur.

Unités de code UTF-16 (str.length) 11 mots de stockage 16 bits
Points de code Unicode ([...str]) 7 Caractères individuels
Clusters de graphèmes (Intl.Segmenter) 1 Glyphes perçus par l'utilisateur
UTF-8 Bytes 25 Octets sérialisés

Décomposition du point de code :

Normes et provenance des sources

Tous les invariants techniques, API et comportements de ce guide sont vérifiés par rapport aux spécifications primaires officielles.

Ecma International

ECMAScript 2024 Language Specification (ECMA-262 15th Edition)

Clause : §6.1.4 The String Type & §22.1 String Objects

Ecma International

ECMAScript Internationalization API (ECMA-402)

Clause : §18 Intl.Segmenter Objects

Unicode Consortium

Unicode Standard Annex #29: Unicode Text Segmentation

Clause : §3 Grapheme Cluster Boundaries

Unicode Consortium

Unicode Standard Annex #15: Unicode Normalization Forms

Clause : §1 Normalization Forms

MDN Web Docs

JavaScript String Reference & UTF-16 Representation

Clause : UTF-16 Characters, Unicode Code Points, and Grapheme Clusters