Vai al contenuto principale
JavaScript (ECMAScript 2024) Standard: Unicode 17.0 / ECMA-262 Time: 8 min di lettura Revisionato: settembre 2024

Unicode in JavaScript: punti di codice

Padroneggiare Unicode in JavaScript: coppie surrogate UTF-16, String.prototype.codePointAt, sequenze di escape di ES2024 e Intl.Segmenter.

TL;DR — Risposta diretta

Risposta tecnica diretta

Le stringhe JavaScript sono sequenze di unità di codice UTF-16 a 16 bit, non caratteri Unicode individuali o simboli percepiti dall'utente. I caratteri supplementari al di fuori del Basic Multilingual Plane (come le emoji e gli ideogrammi rari) richiedono due unità di codice UTF-16 (una coppia surrogata), mentre i grafemi composti richiedono più punti di codice.

💡
Regola di produzione: Non usare mai string.length o string[i] per misurare o tagliare testo visibile all'utente. Usare String.prototype.codePointAt() per i punti di codice completi e Intl.Segmenter per i cluster di grafemi percepiti dall'utente.

La differenza tra unità di codice UTF-16, punti di codice Unicode e cluster di grafemi in 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
Output del programma
2
1
5
3
1

Perché accade: il modello di stringhe di ECMAScript

Per comprendere il comportamento delle stringhe JavaScript, gli sviluppatori devono capire i tre livelli distinti di rappresentazione Unicode nel browser e in Node.js.

1. L'eredità storica di UTF-16

Quando Brendan Eich progettò JavaScript nel 1995, Unicode era definito come una codifica a larghezza fissa di 16 bit (UCS-2) contenente al massimo 65.536 caratteri (il Basic Multilingual Plane, BMP). Nel 1996 Unicode si ampliò a 1.114.112 punti di codice (piani da 0 a 16). Invece di rompere le API esistenti, ECMAScript adottò UTF-16. In UTF-16, i caratteri al di sopra di U+FFFF sono rappresentati come due unità di codice a 16 bit chiamate coppia surrogata: un surrogato alto (\uD800..\uDBFF) seguito da un surrogato basso (\uDC00..\uDFFF).

2. Unità di codice vs punti di codice

Un punto di codice è il valore numerico astratto (U+0000..U+10FFFF). L'emoji faccina sorridente 😀 è il punto di codice U+1F600. Nella memoria di JavaScript, U+1F600 è codificato come le due unità di codice UTF-16 0xD83D e 0xDE00. Metodi come .charCodeAt(0) e la proprietà .length ispezionano solo singole unità a 16 bit, vedendo due unità invece di un carattere.

3. Punti di codice vs cluster di grafemi percepiti dall'utente

Anche l'iterazione per punto di codice tramite Array.from() o for...of è insufficiente per il testo moderno. Le sequenze di emoji composte (come 👨‍👦) consistono di più punti di codice: uomo (U+1F468) + ZWJ (U+200D) + ragazzo (U+1F466). Analogamente, i segni combinanti come «e» (U+0065) + accento acuto combinante (U+0301) formano due punti di codice resi come un unico glifo visibile «é». Solo Intl.Segmenter, conforme a Unicode UAX #29, misura con precisione i caratteri visivi.

4. Surrogati isolati e stringhe mal formate

Poiché le stringhe JavaScript sono sequenze di interi arbitrari a 16 bit, possono contenere surrogati isolati (es. "\uD83D" senza un surrogato basso accompagnatore). ECMAScript consente i surrogati isolati in memoria, ma le API che richiedono UTF-8 ben formato (come TextEncoder, fetch() o WebSockets) sostituiranno i surrogati isolati con U+FFFD (Replacement Character) o genereranno un errore.

Modello di esecuzione di linguaggio e runtime

Linguaggio di destinazione JavaScript
Standard moderno ECMAScript 2024
Versione minima ES2015 (ES6)
Supporto del segmenter Chrome 87+, Safari 14.1+, Firefox 125+, Node.js 16.0+, Bun 1.0+, Deno 1.8+
Ottimizzazione del motore

V8 usa un'ottimizzazione della rappresentazione interna: le stringhe che contengono solo caratteri Latin-1 (U+0000..U+00FF) sono memorizzate come array a 1 byte (SeqOneByteString). Le stringhe che contengono un carattere al di sopra di U+00FF sono promosse ad array UTF-16 a 2 byte (SeqTwoByteString).

Errori comuni vs modelli di produzione

Scopri quali modelli falliscono in produzione e le alternative moderne conformi agli standard.

Contare i caratteri visibili all'utente

Contatore di caratteri in form di interfaccia, limiti SMS, campi biografia.
DA EVITARE: ❌ String.prototype.length
const str = '👨‍💻';
console.log(str.length);
// Returns 5 (UTF-16 code units)
Perché fallisce: String.length conta unità di codice a 16 bit. Le emoji con joiner a larghezza zero e le coppie surrogate riportano conteggi gonfiati.
CONSIGLIATO: ✅ 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)
Perché è migliore: Implementa i confini dei cluster di grafemi estesi di Unicode UAX #29, vincolando correttamente segni combinanti, modificatori e sequenze ZWJ.

Iterare i caratteri di una stringa

Analisi di token, costruzione di trasformatori di stringhe personalizzati.
DA EVITARE: ❌ Ciclo con indice o 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']
Perché fallisce: L'accesso diretto con parentesi quadre text[i] e split("") operano sulle unità di codice, spezzando a metà le coppie surrogate e producendo surrogati isolati non validi.
CONSIGLIATO: ✅ for...of o 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']
Perché è migliore: L'iteratore di stringhe ECMAScript è consapevole dei punti di codice e avanza automaticamente oltre le coppie surrogate alto/basso considerate insieme.

Leggere punti di codice numerici

Convertitori di codifica dei caratteri, hash crittografico, metriche dei font.
DA EVITARE: ❌ charCodeAt(i)
const char = '😀';
console.log(char.charCodeAt(0).toString(16));
// Returns 'd83d' (High surrogate only!)
Perché fallisce: charCodeAt restituisce solo il numero a 16 bit a quell'offset. Per i caratteri supplementari, restituisce solo metà della coppia surrogata.
CONSIGLIATO: ✅ codePointAt(i)
const char = '😀';
console.log(char.codePointAt(0).toString(16));
// Returns '1f600' (True Unicode code point!)
Perché è migliore: codePointAt ispeziona l'unità di codice corrente e la successiva; se formano una coppia surrogata valida, restituisce il valore scalare a 21 bit.

Scrivere sequenze di escape Unicode

Letterali di codice sorgente, payload JSON, definizioni di espressioni regolari.
DA EVITARE: ❌ Escape legacy a coppia surrogata
const fire = '\uD83D\uDD25'; // 🔥
// Prone to transposition, hard to read, manual math needed
Perché fallisce: Richiede agli sviluppatori di calcolare manualmente gli offset esadecimali dei surrogati alto e basso.
CONSIGLIATO: ✅ Escape Unicode con graffe di ES6
const fire = '\u{1F525}'; // 🔥
const smile = '\u{1F600}'; // 😀
Perché è migliore: Accetta direttamente qualsiasi punto di codice Unicode valido da \u{0} a \u{10FFFF} senza calcolo di surrogati.

Matrice dei casi limite e invarianti

Una matrice di test diversificata che confronta il comportamento dei caratteri tra ASCII, segni combinanti, emoji, bandiere e frammenti surrogati.

Caso di test Glifo di input Unità di codice Punti di codice Grafemi Categoria Spiegazione tecnica
Lettera latina ASCII A 1 1 1 Basic Latin U+0041 sta in un singolo byte a 7 bit, un'unità di codice a 16 bit e un punto di codice.
Accento precomposto é 1 1 1 Latin-1 Supplement U+00E9 accento acuto precomposto nel BMP. 1 unità di codice, 1 punto di codice, 1 grafema.
Accento scomposto 2 2 1 Combining Mark Lettera base e (U+0065) + accento acuto combinante (U+0301). 2 punti di codice resi come 1 grafema visivo.
Simbolo di valuta 1 1 1 Currency Segno dell'euro U+20AC situato nel BMP. 1 unità di codice, 1 punto di codice, 1 grafema.
Emoji standard 😀 2 1 1 Supplementary Plane Faccina sorridente U+1F600 nel piano 1. Codificata come 2 unità di codice UTF-16 (D83D DE00).
Sequenza di emoji con ZWJ 👨‍👦 5 3 1 Emoji Sequence Uomo (U+1F468) + ZWJ (U+200D) + ragazzo (U+1F466). 3 punti di codice uniti in 1 glifo di famiglia.
Bandiera dell'arcobaleno 🏳️‍🌈 6 4 1 Emoji Sequence Bandiera bianca (U+1F3F3) + selettore di variazione 16 (U+FE0F) + ZWJ (U+200D) + arcobaleno (U+1F308).
Bandiera nazionale (indicatori regionali) 🇺🇳 4 2 1 Flag Sequence Indicatore regionale U (U+1F1FA) + indicatore regionale N (U+1F1F3). 2 punti di codice, 1 grafema.
Surrogato alto isolato 1 1 1 Surrogate Fragment Surrogato alto isolato 0xD83D. Valido nella memoria di ECMAScript, ma mal formato in UTF-8.

Core API Reference

Standard library methods, runtime signatures, and Unicode semantics.

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

Restituisce il valore del punto di codice Unicode (intero) all'indice di unità di codice UTF-16 specificato.

Semantica Unicode: Se l'indice punta all'inizio di una coppia surrogata valida, restituisce il punto di codice completo a 21 bit (U+0000..U+10FFFF). Se punta a un surrogato basso o a un surrogato solitario, restituisce solo il valore dell'unità di codice surrogata.
⚠️ Avvertenze importanti: Il parametro indice rimane un offset in unità di codice UTF-16, non in punti di codice. Per avanzare carattere per carattere, avanzare di 2 se codePoint >= 0x10000.
String.fromCodePoint(...codePoints: number[]): string

Crea una stringa a partire da una sequenza di punti di codice Unicode a 21 bit.

Semantica Unicode: Calcola ed emette automaticamente le coppie surrogate alto/basso per i punti di codice >= 0x10000.
⚠️ Avvertenze importanti: Genera una RangeError se un punto di codice è negativo, maggiore di 0x10FFFF o NaN.
new Intl.Segmenter(locales?: string | string[], options?: { granularity: "grapheme" | "word" | "sentence" })

Fornisce la segmentazione del testo sensibile alla locale, consentendo un'iterazione affidabile dei cluster di grafemi percepiti dall'utente.

Semantica Unicode: Conforme agli algoritmi predefiniti di segmentazione del testo dell'Unicode Standard Annex #29 (UAX #29).
⚠️ Avvertenze importanti: Disponibile in Chrome 87+, Safari 14.1+, Firefox 125+, Node.js 16.0+. Usare un polyfill per i browser legacy.
str.normalize(form?: "NFC" | "NFD" | "NFKC" | "NFKD"): string

Restituisce la forma di normalizzazione Unicode della stringa chiamante.

Semantica Unicode: Conforme all'Unicode Standard Annex #15 (UAX #15). NFC (predefinita): decomposizione canonica seguita da composizione canonica.
⚠️ Avvertenze importanti: Normalizzare sempre l'input dell'utente a NFC prima di confrontare nomi utente o memorizzare testo in database.
new RegExp(pattern, "v")

Flag UnicodeSets per le espressioni regolari di ECMAScript 2024, che abilita operazioni di insieme e proprietà di stringa.

Semantica Unicode: Sostituisce il flag /u. Supporta \p{Extended_Pictographic} e insiemi di stringhe multi-carattere come [\p{White_Space}--[\n\r]].
⚠️ Avvertenze importanti: Supportato in Chrome 112+, Safari 17+, Firefox 116+, Node.js 20+.
Strumento interattivo

Ispettore interattivo di stringhe e Unicode in JavaScript

Provare qualsiasi stringa o sequenza di emoji in tempo reale per ispezionarne le unità di codice UTF-16, i punti di codice Unicode e i cluster di grafemi nel browser.

Unità di codice UTF-16 (str.length) 11 parole di archiviazione a 16 bit
Punti di codice Unicode ([...str]) 7 Caratteri individuali
Cluster di grafemi (Intl.Segmenter) 1 Glifi percepiti dall'utente
UTF-8 Bytes 25 Ottetti serializzati

Scomposizione del punto di codice:

Standard e provenienza delle fonti

Tutti gli invarianti tecnici, le API e i comportamenti di questa guida sono verificati rispetto alle specifiche primarie ufficiali.

Ecma International

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

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

Ecma International

ECMAScript Internationalization API (ECMA-402)

Clausola: §18 Intl.Segmenter Objects

Unicode Consortium

Unicode Standard Annex #29: Unicode Text Segmentation

Clausola: §3 Grapheme Cluster Boundaries

Unicode Consortium

Unicode Standard Annex #15: Unicode Normalization Forms

Clausola: §1 Normalization Forms

MDN Web Docs

JavaScript String Reference & UTF-16 Representation

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