Conocimiento avanzado
La sección profundiza en las partes internas de las cuerdas. Este conocimiento le será útil si planea trabajar con emoji, caracteres matemáticos o jeroglíficos raros u otros símbolos raros.
Como ya sabemos, las cadenas de JavaScript se basan en Unicode: cada carácter está representado por una secuencia de bytes de 1 a 4 bytes.
JavaScript nos permite insertar un carácter en una cadena especificando su código Unicode hexadecimal con una de estas tres notaciones:
xXX
XX
debe tener dos dígitos hexadecimales con un valor entre 00
y FF
, luego xXX
es el carácter cuyo código Unicode es XX
.
Dado que la notación xXX
sólo admite dos dígitos hexadecimales, sólo se puede utilizar para los primeros 256 caracteres Unicode.
Estos primeros 256 caracteres incluyen el alfabeto latino, la mayoría de los caracteres de sintaxis básica y algunos otros. Por ejemplo, "x7A"
es lo mismo que "z"
(Unicode U+007A
).
alerta( "x7A" ); //z alerta( "xA9" ); // ©, el símbolo de copyright
uXXXX
XXXX
debe tener exactamente 4 dígitos hexadecimales con un valor entre 0000
y FFFF
, luego uXXXX
es el carácter cuyo código Unicode es XXXX
.
Los caracteres con valores Unicode mayores que U+FFFF
también se pueden representar con esta notación, pero en este caso, necesitaremos usar un llamado par sustituto (hablaremos de pares sustitutos más adelante en este capítulo).
alerta( "u00A9" ); // ©, igual que xA9, usando la notación hexadecimal de 4 dígitos alerta( "u044F" ); // я, la letra del alfabeto cirílico alerta( "u2191" ); // ↑, el símbolo de flecha hacia arriba
u{X…XXXXXX}
X…XXXXXX
debe ser un valor hexadecimal de 1 a 6 bytes entre 0
y 10FFFF
(el punto de código más alto definido por Unicode). Esta notación nos permite representar fácilmente todos los caracteres Unicode existentes.
alerta( "u{20331}" ); // 佫, un carácter chino poco común (Unicode largo) alerta( "u{1F60D}" ); // ?, un símbolo de cara sonriente (otro Unicode largo)
Todos los caracteres utilizados con frecuencia tienen códigos de 2 bytes (4 dígitos hexadecimales). Las letras en la mayoría de los idiomas europeos, los números y los conjuntos ideográficos unificados básicos CJK (CJK, de los sistemas de escritura chino, japonés y coreano) tienen una representación de 2 bytes.
Inicialmente, JavaScript se basaba en la codificación UTF-16 que sólo permitía 2 bytes por carácter. Pero 2 bytes sólo permiten 65536 combinaciones y eso no es suficiente para todos los símbolos posibles de Unicode.
Por lo tanto, los símbolos raros que requieren más de 2 bytes se codifican con un par de caracteres de 2 bytes llamados "un par sustituto".
Como efecto secundario, la longitud de dichos símbolos es 2
:
alerta( '?'.longitud ); // 2, GUIÓN MATEMÁTICO CAPITAL X alerta( '?'.longitud ); // 2, CARA CON LÁGRIMAS DE ALEGRÍA alerta( '?'.longitud ); // 2, un carácter chino poco común
Esto se debe a que los pares sustitutos no existían en el momento en que se creó JavaScript y, por lo tanto, el lenguaje no los procesa correctamente.
En realidad, tenemos un solo símbolo en cada una de las cadenas anteriores, pero la propiedad length
muestra una longitud de 2
.
Obtener un símbolo también puede resultar complicado, porque la mayoría de las funciones del lenguaje tratan los pares sustitutos como dos caracteres.
Por ejemplo, aquí podemos ver dos caracteres impares en el resultado:
alerta( '?'[0] ); // muestra símbolos extraños... alerta( '?'[1] ); // ...piezas de la pareja sustituta
Las piezas de una pareja sustituta no tienen significado una sin la otra. Entonces, las alertas del ejemplo anterior en realidad muestran basura.
Técnicamente, los pares sustitutos también son detectables por sus códigos: si un carácter tiene el código en el intervalo 0xd800..0xdbff
, entonces es la primera parte del par sustituto. El siguiente carácter (segunda parte) debe tener el código en el intervalo 0xdc00..0xdfff
. Estos intervalos están reservados exclusivamente para parejas sustitutas por norma.
Entonces, los métodos String.fromCodePoint y str.codePointAt se agregaron en JavaScript para tratar con pares sustitutos.
Son esencialmente iguales que String.fromCharCode y str.charCodeAt, pero tratan correctamente a los pares sustitutos.
Se puede ver la diferencia aquí:
// charCodeAt no reconoce el par sustituto, por lo que proporciona códigos para la primera parte de ?: alerta( '?'.charCodeAt(0).toString(16) ); // d835 // codePointAt reconoce el par sustituto alerta( '?'.codePointAt(0).toString(16) ); // 1d4b3, lee ambas partes del par sustituto
Dicho esto, si tomamos de la posición 1 (y eso es bastante incorrecto aquí), entonces ambos devuelven solo la segunda parte del par:
alerta( '?'.charCodeAt(1).toString(16) ); // dcb3 alerta( '?'.codePointAt(1).toString(16) ); // dcb3 // segunda mitad del par sin sentido
Encontrará más formas de lidiar con pares sustitutos más adelante en el capítulo Iterables. Probablemente también haya bibliotecas especiales para eso, pero nada lo suficientemente famoso como para sugerirlo aquí.
Conclusión: dividir hilos en un punto arbitrario es peligroso
No podemos simplemente dividir una cadena en una posición arbitraria, por ejemplo, tomar str.slice(0, 4)
y esperar que sea una cadena válida, por ejemplo:
alerta ('hola?'.slice(0, 4)); // Hola [?]
Aquí podemos ver un carácter basura (la primera mitad del par sustituto de sonrisa) en la salida.
Sólo tenga en cuenta esto si tiene la intención de trabajar de manera confiable con parejas de alquiler. Puede que no sea un gran problema, pero al menos deberías entender lo que sucede.
En muchos idiomas, hay símbolos que se componen del carácter base con una marca encima o debajo.
Por ejemplo, la letra a
puede ser el carácter base de estos caracteres: àáâäãåā
.
Los caracteres "compuestos" más comunes tienen su propio código en la tabla Unicode. Pero no todos, porque hay demasiadas combinaciones posibles.
Para soportar composiciones arbitrarias, el estándar Unicode nos permite utilizar varios caracteres Unicode: el carácter base seguido de uno o varios caracteres de “marca” que lo “decoran”.
Por ejemplo, si tenemos S
seguida del carácter especial "punto arriba" (código u0307
), se muestra como Ṡ.
alerta ('Su0307'); // S
Si necesitamos una marca adicional encima de la letra (o debajo de ella), no hay problema, simplemente agregue 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": Ṩ
.
Por ejemplo:
alerta ('Su0307u0323'); // S
Esto proporciona una gran flexibilidad, pero también un problema interesante: dos caracteres pueden verse visualmente iguales, pero estar representados con diferentes composiciones Unicode.
Por ejemplo:
sea s1 = 'Su0307u0323'; // Ṩ, S + punto arriba + punto abajo sea s2 = 'Su0323u0307'; // Ṩ, S + punto abajo + punto arriba alerta(`s1: ${s1}, s2: ${s2}`); alerta( s1 == s2 ); // falso aunque los personajes parecen idénticos (?!)
Para resolver esto, existe un algoritmo de "normalización Unicode" que lleva cada cadena a la forma "normal" única.
Se implementa mediante str.normalize().
alerta( "Su0307u0323".normalize() == "Su0323u0307".normalize() ); // verdadero
Es curioso que en nuestra situación normalize()
en realidad reúna una secuencia de 3 caracteres en uno: u1e68
(S con dos puntos).
alerta ("Su0307u0323".normalize().length); // 1 alerta( "Su0307u0323".normalize() == "u1e68" ); // verdadero
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: Formularios de normalización Unicode, pero para la mayoría de los fines prácticos, la información de esta sección es suficiente.