Quais caracteres são agrupados com Array.from?

38

Eu tenho brincado com o JS e não consigo descobrir como o JS decide quais elementos adicionar ao array criado ao usar Array.from(). Por exemplo, o seguinte emoji 👍 tem um lengthde 2, pois é composto por dois pontos de código, mas Array.from()trata esses dois pontos de código como um, fornecendo uma matriz com um elemento:

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

No entanto, alguns outros caracteres também têm dois pontos de código, como esse caractere षि(também possui um .lengthde 2). No entanto, Array.fromnão "agrupa" esse personagem e produz dois elementos:

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

Minha pergunta é: O que determina se o caractere é dividido (como no exemplo dois) ou tratado como um único elemento (como no exemplo um) quando o caractere consiste em dois pontos de código?

Shnick
fonte
5
Dê uma olhada nos pares substitutos UTF-16 ...
Jonas Wilms
11
Eu tenho uma preocupação com o polyfill do MDN do Array.from, que tem um comportamento diferente: -s
Ele
11
@ Ele considera apenas objetos com length. Iteradores ou mesmo Setnão funcionam com isso
adiga 04/02

Respostas:

26

Array.fromprimeiro tenta invocar o iterador do argumento, se ele tiver um, e as strings possuem iteradores, portanto, ele invoca String.prototype[Symbol.iterator], então vamos ver como o método prototype funciona. Está descrito na especificação aqui :

  1. Seja O seja? RequireObjectCoercible (este valor).
  2. Vamos ser ? ToString (O).
  3. Retorne CreateStringIterator (S).

Olhar para cima CreateStringIteratorleva você a 21.1.5.2.1 %StringIteratorPrototype%.next ( ), o que faz:

  1. Seja cp! CodePointAt (s, posição).
  2. Seja resultString o valor da String que contém cp. [[CodeUnitCount]] unidades de código consecutivas desde s começando com a unidade de código na posição do índice.
  3. Defina O. [[StringNextIndex]] para a posição + cp. [[CodeUnitCount]].
  4. Retorno CreateIterResultObject (resultString, false).

É nissoCodeUnitCount que você está interessado. Esse número vem do CodePointAt :

  1. Seja primeiro a unidade de código na posição do índice dentro da string.
  2. Seja cp o ponto do código cujo valor numérico é o primeiro.
  3. Se primeiro não for um substituto líder ou um substituto final, então

    uma. Retorne o registro { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: false }.

  4. Se primeiro for um substituto à direita ou uma posição + 1 = tamanho, então

    a.Retorne o registro { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }.

  5. Em segundo lugar, seja a unidade de código na posição de índice + 1 dentro da string.

  6. Se o segundo não for um substituto final, então

    uma. Retorne o registro { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }.

  7. Defina cp como! UTF16DecodeSurrogatePair (primeiro, segundo).

  8. Retorne o registro { [[CodePoint]]: cp, [[CodeUnitCount]]: 2, [[IsUnpairedSurrogate]]: false }.

Portanto, ao iterar sobre uma string com Array.from, ele retornará um CodeUnitCount de 2 somente quando o caractere em questão for o início de um par substituto. Caracteres que são interpretados como pares substitutos são descritos aqui :

Tais operações aplicam tratamento especial a todas as unidades de código com um valor numérico no intervalo inclusivo de 0xD800 a 0xDBFF (definido pelo Padrão Unicode como um substituto principal ou mais formalmente como um código de alto substituto) e todas as unidades de código com um valor numérico no intervalo inclusivo de 0xDC00 a 0xDFFF (definido como um substituto à direita ou mais formalmente como uma unidade de código com um substituto baixo) usando as seguintes regras ..:

षि não é um par substituto:

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

Mas 👍os personagens são:

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

O primeiro código de caractere de '👍'é, em hexadecimal, D83D, que está dentro do intervalo 0xD800 to 0xDBFFdos principais substitutos. Por outro lado, o primeiro código de caractere de 'षि'é muito menor e não é. Então, isso 'षि'se divide, mas '👍'não acontece.

षिé composto de dois caracteres separados: , Devanagari Carta Ssa , e ि, Devanagari vogal Sinal I . Quando próximos um do outro nesta ordem, eles são combinados graficamente em um único caractere visualmente, apesar de serem compostos por dois caracteres separados.

Por outro lado, os códigos de caracteres 👍 fazem sentido quando juntos como um único glifo. Se você tentar usar uma string com um ponto de código sem o outro, receberá um símbolo sem sentido:

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

CertainPerformance
fonte
10
Penso que, embora a maioria esteja correta, útil e com citações fornecidas com cuidado, essa resposta falha em explicar claramente a principal diferença entre os dois casos: do ponto de vista do Unicode, षिna verdade são dois caracteres com pontos de código distintos combinados para formar um único glifo (um caractere abstrato , como entendido pelos seres humanos). Isso contrasta com o 👍emoji, que é um caractere completo por si só, mesmo que seu ponto de código seja alto o suficiente para ser dividido em um par substituto. Acredito que esclarecer isso poderia ajudar muito (de outro modo, valioso) a responder.
rhino
Especificamente, a consoante ष (ṣ) e a vogal ि (i) se combinam graficamente na sílaba षि (ṣi)
Amadan
@CertainPerformance Existe apenas um ponto de código em "👍". Isso sugere que a terminologia nesta resposta pode estar incorreta.
Ben Aston
13

UTF-16 (a codificação usada para seqüências de caracteres em js) usa unidades de 16 bits. Portanto, todo unicode que pode ser representado usando 15 bits é representado como um ponto de código, e todo o resto como dois, conhecidos como pares substitutos . O iterador de strings itera sobre pontos de código.

UTF-16 na Wikipedia

Jonas Wilms
fonte
8

É tudo sobre o código por trás dos personagens. Alguns são codificados em dois bytes (UTF-16) e são interpretados Array.fromcomo dois caracteres. Preciso verificar a lista dos personagens:

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 a função que exibe o código hexadecimal:

Javascript: string Unicode para hexadecimal

Grégory NEUT
fonte