¿Qué caracteres se agrupan con Array.from?

38

He estado jugando con JS y no puedo entender cómo JS decide qué elementos agregar a la matriz creada cuando se usa Array.from(). Por ejemplo, el siguiente emoji 👍 tiene un length2, ya que está hecho de dos puntos de código, pero Array.from()trata estos dos puntos de código como uno, dando una matriz con un elemento:

const emoji = '👍';
console.log(Array.from(emoji)); // Output: ["👍"]

Sin embargo, algunos otros personajes también tienen dos puntos de código como este personaje षि(también tiene un .lengthde 2). Sin embargo, Array.fromno "agrupa" este personaje y en su lugar produce dos elementos:

const str = 'षि';
console.log(Array.from(str)); // Output: ["ष", "ि"]

Mi pregunta es: ¿Qué determina si el carácter se divide (como en el ejemplo dos) o se trata como un solo elemento (como en el ejemplo uno) cuando el carácter consta de dos puntos de código?

Shnick
fuente
55
Echa un vistazo a los pares sustitutos de UTF-16 ...
Jonas Wilms
1
Me preocupa el polyfill de MDN de Array.from, que tiene un comportamiento diferente: -s
Ele
1
@Ele solo considera objetos con length. Iteradores o incluso Setno funciona con eso
adiga

Respuestas:

26

Array.fromprimero intenta invocar el iterador del argumento si tiene uno, y las cadenas tienen iteradores, por lo que invoca String.prototype[Symbol.iterator], así que veamos cómo funciona el método prototipo. Se describe en la especificación aquí :

  1. Deja que O sea? RequireObjectCoercible (este valor).
  2. Seamos ? ToString (O).
  3. Devuelve CreateStringIterator (S).

Mirar hacia arriba CreateStringIteratorfinalmente te lleva a lo 21.1.5.2.1 %StringIteratorPrototype%.next ( )que hace:

  1. Deja que cp sea! CodePointAt (s, posición).
  2. Deje que resultString sea el valor de cadena que contiene cp. [[CodeUnitCount]] unidades de código consecutivas desde s que comienzan con la unidad de código en la posición de índice.
  3. Establezca O. [[StringNextIndex]] en la posición + cp. [[CodeUnitCount]].
  4. Devuelve CreateIterResultObject (resultString, false).

Esto CodeUnitCountes lo que le interesa. Este número proviene de CodePointAt :

  1. Sea primero la unidad de código en la posición de índice dentro de la cadena.
  2. Deje que cp sea el punto de código cuyo valor numérico sea el primero.
  3. Si primero no es un sustituto principal o sustituto final, entonces

    a. Devuelve el registro { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: false }.

  4. Si primero es un sustituto o posición final + 1 = tamaño, entonces

    a. Devuelva el registro { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }.

  5. Sea el segundo la unidad de código en la posición de índice + 1 dentro de la cadena.

  6. Si el segundo no es un sustituto final, entonces

    a. Devuelve el registro { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }.

  7. Establecer cp a! UTF16DecodeSurrogatePair (primero, segundo).

  8. Devuelve el registro { [[CodePoint]]: cp, [[CodeUnitCount]]: 2, [[IsUnpairedSurrogate]]: false }.

Entonces, al iterar sobre una cadena con Array.from, devuelve un CodeUnitCount de 2 solo cuando el carácter en cuestión es el comienzo de un par sustituto. Los caracteres que se interpretan como pares sustitutos se describen aquí :

Dichas operaciones aplican un tratamiento especial a cada unidad de código con un valor numérico en el rango inclusivo 0xD800 a 0xDBFF (definido por el Estándar Unicode como un sustituto principal , o más formalmente como una unidad de código de alto sustituto) y cada unidad de código con un valor numérico en el rango inclusivo 0xDC00 a 0xDFFF (definido como un sustituto final, o más formalmente como una unidad de código de bajo sustituto) utilizando las siguientes reglas ..:

षि no es un par sustituto:

console.log('षि'.charCodeAt()); // First character code: 2359, or 0x937
console.log('षि'.charCodeAt(1)); // Second character code: 2367, or 0x93F

Pero 👍los personajes son:

console.log('👍'.charCodeAt()); // 55357, or 0xD83D
console.log('👍'.charCodeAt(1)); // 56397, or 0xDC4D

El primer código de carácter de '👍'es, en hexadecimal, D83D, que está dentro del rango 0xD800 to 0xDBFFde los sustitutos principales. Por el contrario, el primer código de caracteres 'षि'es mucho más bajo y no lo es. Entonces 'षि'se separa, pero '👍'no lo hace.

षिse compone de dos caracteres distintos: , devanagari Carta Ssa , y ि, devanagari vocal sesión I . Cuando están uno al lado del otro en este orden, se combinan gráficamente en un solo personaje visualmente, a pesar de estar compuestos por dos personajes separados.

En contraste, los códigos de caracteres 👍 solo tienen sentido cuando están juntos como un solo glifo. Si intenta usar una cadena con cualquier punto de código sin el otro, obtendrá un símbolo sin sentido:

console.log('👍'[0]);
console.log('👍'[1]);

Cierto rendimiento
fuente
10
Creo que, aunque en su mayoría es correcto, útil y con citas cuidadosamente proporcionadas, esta respuesta no explica claramente la diferencia clave entre los dos casos: desde el punto de vista de Unicode, en षिrealidad son dos caracteres con puntos de código distintos combinados para formar un solo glifo (un carácter abstracto , tal como lo entienden los humanos). Esto está en contraste con el 👍emoji, que es un personaje completo en sí mismo, a pesar de que su punto de código es lo suficientemente alto como para que deba dividirse en un par sustituto. Creo que aclarar eso podría ayudar mucho a esta respuesta (por lo demás valiosa).
rinoceronte
Específicamente, la consonante ष (ṣ) y la vocal ि (i) se combinan gráficamente en la sílaba षि (ṣi)
Amadan
@CertainPerformance Solo hay un punto de código en "👍". Esto sugiere que la terminología en esta respuesta podría ser incorrecta.
Ben Aston
13

UTF-16 (la codificación utilizada para cadenas en js) utiliza unidades de 16 bits. Por lo tanto, cada unicode que se puede representar con 15 bits se representa como un punto de código, todo lo demás como dos, conocidos como pares sustitutos . El iterador de cadenas itera sobre puntos de código.

UTF-16 en Wikipedia

Jonas Wilms
fuente
8

Se trata del código detrás de los personajes. Algunos están codificados en dos bytes (UTF-16) y se interpretan Array.fromcomo dos caracteres. Tengo que revisar la lista de los personajes:

http://www.fileformat.info/info/charset/UTF-8/list.htm

http://www.fileformat.info/info/charset/UTF-16/list.htm

function displayHexUnicode(s) {
  console.log(s.split("").reduce((hex,c)=>hex+=c.charCodeAt(0).toString(16).padStart(4,"0"),""));
}

displayHexUnicode('षि');

console.log(Array.from('षि').forEach(x => displayHexUnicode(x)));


function displayHexUnicode(s) {
  console.log(s.split("").reduce((hex,c)=>hex+=c.charCodeAt(0).toString(16).padStart(4,"0"),""));
}

displayHexUnicode('👍');

console.log(Array.from('👍').forEach(x => displayHexUnicode(x)));


Para la función que muestra el código hexadecimal:

Javascript: cadena Unicode a hexadecimal

Grégory NEUT
fuente