Esta sección ahonda en los interioridades de los string. Este conocimiento será útil para ti si planeas lidiar con emojis, raros caracteres matemáticos, jeroglÃficos, u otros sÃmbolos extraños.
Como ya mencionamos, los strings de JavaScript están basados en Unicode: cada carácter está representado por una secuencia de entre 1 y 4 bytes.
JavaScript nos permite insertar un carácter en un string por medio de su código hexadecimal Unicode, usando estas tres notaciones:
-
\xXXXXdeben ser dos dÃgitos hexadecimales con un valor entre00yFF. Entonces,\xXXes el carácter cuyo código Unicode esXX.Como la notación
\xXXadmite solo dos dÃgitos hexadecimales, puede representar solamente los primeros 256 caracteres Unicode.Estos primeros 256 caracteres incluyen el alfabeto latino, la mayorÃa de caracteres de sintaxis básicos, y algunos otros. Por ejemplo,
"\x7A"es lo mismo que"z"(UnicodeU+007A).alert( "\x7A" ); // z alert( "\xA9" ); // ©, el sÃmbolo de copyright -
\uXXXXXXXXdeben ser exactamente 4 dÃgitos hexadecimales con un valor entre0000yFFFF. Entonces,\uXXXXes el carácter cuyo código Unicode esXXXX.Caracteres con un valor Unicode mayor que
U+FFFFtambién pueden ser representados con esta notación, pero en ese caso necesitamos usar los llamados âpares sustitutosâ, descritos más adelante.alert( "\u00A9" ); // ©, lo mismo que \xA9, usando la notación de 4 dÃgitos hexa alert( "\u044F" ); // Ñ, letra del alfabeto cirÃlico alert( "\u2191" ); // â, sÃmbolo flecha -
\u{Xâ¦XXXXXX}Xâ¦XXXXXXdebe ser un valor hexadecimal de 1 a 6 bytes entre0y10FFFF(el mayor punto de código definido por Unicode). Esta notación nos permite fácilmente representar todos los caracteres Unicode existentes.alert( "\u{20331}" ); // 佫, un raro carácter chino alert( "\u{1F60D}" ); // ð, un sÃmbolo de cara sonriente
Pares sustitutos
Todos los caracteres frecuentes tienen códigos de 2 bytes (4 dÃgitos hexa). Las letras de la mayorÃa de los lenguajes europeos, números, los conjuntos básicos de caracteres ideográficos CJK unificados (CJK: de los sistemas chino, japonés y coreano), tienen un representación de 2 bytes.
Inicialmente, JavaScript estaba basado en la codificación UTF-16 que solo permite 2 bytes por carácter. Pero 2 bytes solo permiten 65536 combinaciones y eso no es suficiente para cada sÃmbolo Unicode posible.
Entonces, los sÃmbolos raros que requieren más de 2 bytes son codificados con un par de caracteres de 2 bytes llamado âpar sustitutoâ.
Como efecto secundario, el largo de tales sÃmbolos es 2:
alert( 'ð³'.length ); // 2, carácter matemático X capitalizado
alert( 'ð'.length ); // 2, cara con lágrimas de risa
alert( '𩷶'.length ); // 2, un raro carácter chino
Esto es porque los pares sustitutos no existÃan cuando JavaScript fue creado, por ello no es procesado correctamente por el lenguaje.
En realidad tenemos un solo sÃmbolo en cada lÃnea de los string de arriba, pero la propiedad length los muestra con un largo de 2.
Obtener un sÃmbolo puede ser intrincado, porque la mayorÃa de las caracterÃsticas del lenguaje trata a los pares sustitutos como de 2 caracteres.
Por ejemplo, aquà vemos dos caracteres extraños en la salida:
alert( 'ð³'[0] ); // muestra sÃmbolos extraños...
alert( 'ð³'[1] ); // ...partes del par sustituto
Las 2 partes del par sustituto no tienen significado el uno sin el otro. Entonces las alertas del ejemplo en realidad muestran basura.
Técnicamente, los pares sustitutos son también detectables por su propio código: si un carácter tiene código en el intervalo de 0xd800..0xdbff, entonces es la primera parte de un par sustituto. El siguiente carácter (segunda parte) debe tener el código en el intervalo 0xdc00..0xdfff. Estos intervalos son reservados exclusivamente para pares sustitutos por el estándar.
Los métodos String.fromCodePoint y str.codePointAt fueron añadidos en JavaScript para manejar los pares sustitutos.
Esencialmente, son lo mismo que String.fromCharCode y str.charCodeAt, pero tratan a los pares sustitutos correctamente.
Se puede ver la diferencia aquÃ:
// charCodeAt no percibe los pares sustitutos, entonces da el código de la primera parte de ð³:
alert( 'ð³'.charCodeAt(0).toString(16) ); // d835
// codePointAt reconoce los pares sustitutos
alert( 'ð³'.codePointAt(0).toString(16) ); // 1d4b3, lee ambas partes del par sustituto
Dicho esto, si tomamos desde la posición 1 (y hacerlo es incorrecto aquÃ), ambas funciones devolverán solo la segunda parte del par:
alert( 'ð³'.charCodeAt(1).toString(16) ); // dcb3
alert( 'ð³'.codePointAt(1).toString(16) ); // dcb3
// segunda parte del par, sin sentido
Encontrarás más formas de trabajar con pares sustitutos más adelante en el capÃtulo Iterables. Probablemente hay bibliotecas especiales para eso también, pero nada lo suficientemente famoso como para sugerirlo aquÃ.
No podemos simplemente separar un string en una posición arbitraria, por ejemplo tomar str.slice(0, 4), y confiar en que sea un string válido:
alert( 'hi ð'.slice(0, 4) ); // hi [?]
Aquà podemos ver basura (la primera mitad del par sustituto de la sonrisa) en la salida.
Simplemente sé consciente de esto si quieres trabajar con confianza con los pares sustitutos. Puede que no sea un gran problema, pero al menos deberÃas entender lo que pasa.
Marcas diacrÃticas y normalización
En muchos idiomas hay sÃmbolos compuestos, con un carácter de base y una marca arriba o debajo.
Por ejemplo, la letra a puede ser el carácter base para estos caracteres: à áâäãåÄ.
Los caracteres âcompuestosâ más comunes tienen su propio código en la tabla UTF-16. Pero no todos ellos, porque hay demasiadas combinaciones posibles.
Para soportar composiciones arbitrarias, el estándar Unicode permite usar varios caracteres Unicode: el carácter base y uno o varios caracteres de âmarcaâ que lo âdecoranâ.
Por ejemplo, si tenemos S seguido del carácter especial âpunto arribaâ (código \u0307), se muestra como á¹ .
alert('S\u0307'); // SÌ
Si necesitamos una marca adicional sobre la letra (o debajo de ella), no hay problema, simplemente se agrega el carácter de marca necesario.
Por ejemplo, si agregamos un carácter âpunto debajoâ (código \u0323), entonces tendremos" S con puntos arriba y abajo ": Ṩ.
Ejemplo:
alert( 'S\u0307\u0323' ); // SÌÌ£
Esto proporciona una gran flexibilidad, pero también un problema interesante: dos caracteres pueden ser visualmente iguales, pero estar representados con diferentes composiciones Unicode.
Por ejemplo:
let s1 = 'S\u0307\u0323'; // SÌÌ£, S + punto arriba + punto debajo
let s2 = 'S\u0323\u0307'; // SÌÌ£, S + punto debajo + punto arriba
alert( `s1: ${s1}, s2: ${s2}` );
alert( s1 == s2 ); // false aunque los caracteres se ven idénticos (?!)
Para resolver esto, existe un algoritmo de ânormalización Unicodeâ que lleva cada cadena a la forma ânormalâ.
Este es implementado por str.normalize().
alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true
Lo curioso de esta situación particular es que normalize () reúne una secuencia de 3 caracteres en uno: \u1e68 (S con dos puntos).
alert( "S\u0307\u0323".normalize().length ); // 1
alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true
En realidad, este no es siempre el caso. La razón es que el sÃmbolo Ṩ es âbastante comúnâ, por lo que los creadores de Unicode lo incluyeron en la tabla principal y le dieron el código.
Si desea obtener más información sobre las reglas y variantes de normalización, se describen en el apéndice del estándar: Unicode, pero para la mayorÃa de los propósitos prácticos, la información de esta sección es suficiente.
Comentarios
<code>, para varias lÃneas â envolverlas en la etiqueta<pre>, para más de 10 lÃneas â utilice una entorno controlado (sandbox) (plnkr, jsbin, codepenâ¦)