UTF-16 - la codificación detrás de las cadenas de JavaScript
Este artículo se lee en unos 4 minutos.
Si tu código JavaScript dice que "🌸".length es 2, ya te has encontrado con UTF-16. Esta codificación es invisible hasta que los emojis entran en escena.UTF-16 es una codificación Unicode que representa la mayoría de caracteres en 2 bytes y los caracteres suplementarios (incluidos la mayoría de emojis) en 4 bytes mediante pares sustitutos. Es la representación interna de cadenas en JavaScript, Java, C# y partes de Windows. Entender sus peculiaridades es esencial para manejar emojis correctamente.
Definición
UTF-16 (16-bit Unicode Transformation Format) codifica puntos de código Unicode en secuencias de unidades de código de 16 bits. Los puntos de código del Plano Multilingüe Básico (BMP, U+0000 a U+FFFF) ocupan una unidad de 16 bits (2 bytes). Los puntos de código por encima de U+FFFF se codifican como pares sustitutos - dos unidades de 16 bits juntas (4 bytes en total).
La estructura de dos niveles
| Rango de puntos de código | Unidades de código | Bytes | Ejemplos |
|---|---|---|---|
| U+0000 - U+FFFF (BMP) | 1 | 2 | ASCII, latín, caracteres CJK |
| U+10000 - U+10FFFF | 2 (par sustituto) | 4 | La mayoría de emojis, escrituras antiguas |
Pares sustitutos
Para puntos de código por encima de U+FFFF, UTF-16 divide el valor en dos unidades de código de 16 bits:
- Sustituto alto: U+D800 a U+DBFF (1024 valores posibles)
- Sustituto bajo: U+DC00 a U+DFFF (1024 valores posibles)
La combinación produce 1024 × 1024 = 1 048 576 puntos de código posibles, cubriendo exactamente el plano suplementario (U+10000 a U+10FFFF). Los valores sustitutos están reservados y nunca aparecen como caracteres independientes en texto Unicode válido.
Ejemplo: 🌸 (U+1F338)
El emoji de flor de cerezo en el punto de código U+1F338 se codifica como:
- Sustituto alto: 0xD83C
- Sustituto bajo: 0xDF38
- Bytes UTF-16:
D8 3C DF 38(4 bytes en total)
En una cadena JavaScript, esto se almacena como dos unidades de código. "🌸".length devuelve 2 porque length cuenta unidades de código, no puntos de código.
Dónde se usa UTF-16
- JavaScript:
Stringinternamente;length,charAt,charCodeAtoperan sobre unidades de código - Java:
Stringycharson UTF-16 - C# / .NET:
stringes UTF-16 - API de Windows: muchas APIs usan UTF-16 para cadenas
- Qt, Cocoa, ICU: UTF-16 internamente para muchas operaciones de cadenas
UTF-8, en cambio, es el formato dominante en la transmisión (HTTP, JSON, archivos). Muchos sistemas usan UTF-8 para almacenamiento y transporte, y UTF-16 para el procesamiento en memoria.
Las trampas en JavaScript
length vs. caracteres visibles
"a".length // 1
"あ".length // 1 (BMP)
"🌸".length // 2 (par sustituto)
"👨👩👧".length // 8 (3 emojis + 2 ZWJ en pares sustitutos)
[..."🌸"].length // 1 (iteración por puntos de código)
[..."👨👩👧"].length // 5 (puntos de código, no grafemas)Invertir cadenas rompe los emojis
"🌸".split("").reverse().join("")
// Roto: los sustitutos se separan, se muestra como ?? o caracteres inválidos
[..."🌸"].reverse().join("")
// Correcto: inversión consciente de puntos de código
// Aún mejor: usar Intl.Segmenter para clústeres de grafemascharCodeAt devuelve unidades de código sustituto
"🌸".charCodeAt(0) devuelve 55356 (0xD83C, el sustituto alto), no el punto de código 127800. Usa codePointAt(0) para obtener el punto de código real, que combina los sustitutos.
Comparación UTF-8 vs. UTF-16
| Aspecto | UTF-8 | UTF-16 |
|---|---|---|
| Tamaño ASCII | 1 byte | 2 bytes |
| Tamaño CJK | 3 bytes | 2 bytes |
| Tamaño emoji | 4 bytes | 4 bytes |
| Autosincronización | Sí | Sí (mediante rangos sustitutos) |
| Endianness | Ninguno | Existen variantes BE/LE |
Errores comunes
- ❌ «UTF-16 siempre usa 2 bytes por carácter» → ✅ Los caracteres suplementarios usan 4 bytes mediante pares sustitutos
- ❌ «
str.lengthdevuelve el número de caracteres» → ✅ Devuelve el número de unidades de código en UTF-16 - ❌ «UTF-16 se está quedando obsoleto» → ✅ Está profundamente integrado en JavaScript, Java, .NET y las APIs de Windows, y no va a desaparecer pronto