Unicode y Emoji: de puntos de código a renderizado
Unicode: el estándar de codificación que unifica todos los caracteres
Unicode asigna un número único (punto de código) a cada carácter de casi todos los sistemas de escritura del mundo.
| Concepto | Descripción | Ejemplo |
|---|---|---|
| Punto de código (Code Point) | Número único del carácter | U+4E2D (中) |
| Plano de código | Cada 65536 puntos de código forma un plano | BMP (plano 0) |
| BMP | Plano multilingüe básico, U+0000~U+FFFF | La mayoría de caracteres comunes |
| Plano suplementario | U+10000~U+10FFFF | Emoji, escrituras antiguas, caracteres raros |
1. Familia de codificaciones UTF
| Codificación | Longitud en bytes | Codificación de "中" | Características |
|---|---|---|---|
| UTF-8 | 1-4 bytes | E4 B8 AD | Longitud variable, compatible con ASCII, preferido para Web |
| UTF-16 | 2 o 4 bytes | 4E2D | Longitud variable, uso interno en JS |
| UTF-32 | 4 bytes fijo | 00004E2D | Longitud fija, alto consumo de memoria |
Reglas de codificación UTF-8
| Rango de puntos de código | Bytes | Plantilla de codificación |
|---|---|---|
| U+0000 ~ U+007F | 1 | 0xxxxxxx |
| U+0080 ~ U+07FF | 2 | 110xxxxx 10xxxxxx |
| U+0800 ~ U+FFFF | 3 | 1110xxxx 10xxxxxx 10xxxxxx |
| U+10000 ~ U+10FFFF | 4 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
Ejemplo de codificación
Carácter: 中
Punto de código: U+4E2D = 0100 1110 0010 1101
UTF-8 (3 bytes):
1110xxxx 10xxxxxx 10xxxxxx
1110 0100 10 111000 10 101101
= E4 B8 AD
UTF-16 (2 bytes):
4E2D
UTF-32 (4 bytes):
00004E2D
2. Trampas de Unicode en JavaScript
Longitud de cadena incorrecta
'中'.length // 1 ✅
'𠮷'.length // 2 ❌ (carácter del plano suplementario, par suplente)
'👨👩👧👦'.length // 11 ❌ (secuencia ZWJ de Emoji)
'🏳️🌈'.length // 6 ❌ (Emoji de bandera)
Causa: JS usa UTF-16
Los caracteres del plano suplementario (punto de código > U+FFFF) requieren pares suplentes (2 unidades de código UTF-16):
Carácter: 𠮷 (U+20BB7)
Par suplente UTF-16: D842 DF87
JS: length = 2 (dos unidades de código)
Forma correcta de procesar
// Método 1: operador de expansión
[...'𠮷'].length // 1 ✅
[...'👨👩👧👦'].length // 1 ✅ (pero la secuencia ZWJ se cuenta como uno)
// Método 2: Array.from
Array.from('𠮷').length // 1 ✅
// Método 3: for...of
for (const char of '𠮷👨👩👧👦') {
console.log(char); // iteración carácter por carácter
}
Operaciones con puntos de código
// Obtener punto de código
'中'.codePointAt(0) // 20013 (0x4E2D)
'𠮷'.codePointAt(0) // 134047 (0x20BB7)
// Crear carácter desde punto de código
String.fromCodePoint(0x4E2D) // '中'
String.fromCodePoint(0x20BB7) // '𠮷'
// Nota: charCodeAt no puede procesar el plano suplementario
'𠮷'.charCodeAt(0) // 55362 (solo el primer suplente)
3. Análisis profundo de la codificación Emoji
Cuatro tipos de Emoji
| Tipo | Ejemplo | Punto de código | Longitud |
|---|---|---|---|
| Emoji básico | 😀 | U+1F600 | 1 (punto de código) |
| Emoji con modificador | 👩🏽 | U+1F469 + U+1F3FD | 2 (puntos de código) |
| Secuencia ZWJ | 👨👩👧👦 | Múltiples puntos de código + ZWJ | 7 (puntos de código) |
| Emoji de bandera | 🇨🇳 | U+1F1E8 + U+1F1F3 | 2 (puntos de código) |
Conector de ancho cero (ZWJ)
ZWJ (U+200D) conecta múltiples Emoji en un Emoji compuesto:
👨 (U+1F468) + ZWJ (U+200D) + 👩 (U+1F469) + ZWJ (U+200D) + 👧 (U+1F467) + ZWJ (U+200D) + 👦 (U+1F466)
= 👨👩👧👦 (familia de cuatro)
Modificadores de tono de piel
Los modificadores de tono de piel (U+1F3FB ~ U+1F3FF) cambian el tono de piel del Emoji:
👩 (U+1F469) + 🏽 (U+1F3FD) = 👩🏽 (mujer con tono de piel medio)
| Modificador | Punto de código | Tono de piel |
|---|---|---|
| U+1F3FB | 🏻 | Tono claro |
| U+1F3FC | 🏼 | Tono medio claro |
| U+1F3FD | 🏽 | Tono medio |
| U+1F3FE | 🏾 | Tono medio oscuro |
| U+1F3FF | 🏿 | Tono oscuro |
Emoji de bandera
Las banderas se componen de dos símbolos indicadores regionales (ISO 3166-1 alpha-2):
🇨 (U+1F1E8) + 🇳 (U+1F1F3) = 🇨🇳 (China)
🇺 (U+1F1FA) + 🇸 (U+1F1F8) = 🇺🇸 (Estados Unidos)
🇯 (U+1F1EF) + 🇵 (U+1F1F5) = 🇯🇵 (Japón)
4. Selectores de variante
VS16 (U+FE0F) convierte la presentación de texto a presentación Emoji:
❤ (U+2764) → presentación de texto: ❤ (corazón negro)
❤️ (U+2764 + U+FE0F) → presentación Emoji: ❤️ (corazón rojo)
| Carácter | Sin VS16 | Con VS16 |
|---|---|---|
| ☕ | ☕ (texto) | ☕️ (Emoji) |
| ⚡ | ⚡ (texto) | ⚡️ (Emoji) |
| ❤ | ❤ (texto) | ❤️ (Emoji) |
5. Uso de bibliotecas de herramientas para procesar Unicode
Codificación y decodificación Unicode
- Abre la herramienta de codificación/decodificación Unicode
- Ingresa texto o punto de código
- Revisa los resultados de la conversión:
- Texto → secuencia de escape Unicode (
\u4E2D) - Punto de código → carácter
- Secuencia de bytes UTF-8/16/32
- Texto → secuencia de escape Unicode (
Codificación Emoji
- Abre la herramienta de codificación Emoji
- Ingresa un Emoji
- Revisa la información completa de codificación:
- Secuencia de puntos de código
- Bytes UTF-8
- Descomposición ZWJ/modificadores
- Nombre corto CLDR
6. Trampas comunes y soluciones
Trampa 1: Truncamiento de cadenas
// Error: el truncamiento puede cortar pares suplentes
text.substring(0, 10) // puede producir caracteres inválidos
// Correcto: truncar por punto de código
function sliceByCodePoint(str, start, end) {
const chars = [...str];
return chars.slice(start, end).join('');
}
Trampa 2: Inversión de cadenas
// Error: la inversión destruye caracteres combinados
'👨👩👧👦'.split('').reverse().join('') // texto ilegible
// Correcto: invertir por punto de código
[...'👨👩👧👦'].reverse().join('') // ✅
Trampa 3: Coincidencia con expresiones regulares
// Error: . no coincide con caracteres del plano suplementario
/^.$/.test('𠮷') // false
// Correcto: usar flag u
/^.$/u.test('𠮷') // true
Trampa 4: Ordenación
// Ordenación por defecto según unidades de código UTF-16
['中', '𠮷', 'a'].sort() // puede no ser el esperado
// Correcto: ordenar por punto de código
['中', '𠮷', 'a'].sort((a, b) =>
a.codePointAt(0) - b.codePointAt(0)
)
7. API prácticas
// Normalización (manejar equivalencia de caracteres combinados)
'café'.normalize('NFC') // café (combinado)
'café'.normalize('NFD') // café (descompuesto)
// Detectar Emoji
function isEmoji(char) {
const code = char.codePointAt(0);
return (code >= 0x1F600 && code <= 0x1F64F) || // Emoticonos
(code >= 0x1F300 && code <= 0x1F5FF) || // Símbolos varios
(code >= 0x1F680 && code <= 0x1F6FF) || // Transporte
(code >= 0x1F900 && code <= 0x1F9FF); // Suplementario
}
// Obtener la longitud real de los caracteres
function graphemeLength(str) {
const segmenter = new Intl.Segmenter('es', { granularity: 'grapheme' });
return [...segmenter.segment(str)].length;
}
graphemeLength('👨👩👧👦') // 1 ✅
graphemeLength('🏳️🌈') // 1 ✅
#Unicode#Emoji#UTF-8#字符编码#码点