Jakie postacie są zgrupowane w Array.from?

38

Bawiłem się z JS i nie mogę zrozumieć, w jaki sposób JS decyduje, które elementy dodać do utworzonej tablicy podczas używania Array.from(). Na przykład następujący emoji 👍 ma wartość length2, ponieważ składa się z dwóch punktów kodowych, ale Array.from()traktuje te dwa punkty kodowe jako jeden, dając tablicę z jednym elementem:

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

Jednak niektóre inne znaki mają również dwa punkty kodowe, takie jak ten znak षि(również ma .length2). Jednak Array.fromnie „grupuje” tej postaci i zamiast tego wytwarza dwa elementy:

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

Moje pytanie brzmi: co decyduje o tym, czy znak jest podzielony (jak w przykładzie 2), czy traktowany jako jeden pojedynczy element (jak w przykładzie jeden), gdy znak składa się z dwóch punktów kodowych?

Shnick
źródło
5
Spójrz na pary zastępcze UTF-16 ...
Jonas Wilms
1
Obawiam się, że MDF's polfill Array.from, który ma inne zachowanie: -s
Ele
1
@Ele uwzględnia tylko obiekty z length. Iteratory, a nawet Setnie działają z tym
adiga

Odpowiedzi:

26

Array.fromNajpierw próbuje wywołać iterator argumentu, jeśli ma jeden, a łańcuchy mają iteratory, więc wywołuje String.prototype[Symbol.iterator], więc sprawdźmy, jak działa metoda prototypowa. Jest to opisane w specyfikacji tutaj :

  1. Niech O będzie? RequireObjectCoercible (ta wartość).
  2. Bądźmy ? ToString (O).
  3. Zwróć CreateStringIterator (S).

Wyszukiwanie w CreateStringIteratorkońcu prowadzi do tego 21.1.5.2.1 %StringIteratorPrototype%.next ( ), co:

  1. Niech cp będzie! CodePointAt (s, position).
  2. Niech resultString będzie wartością String zawierającą cp. [[CodeUnitCount]] kolejne jednostki kodu od s zaczynające się od jednostki kodu w pozycji indeksu.
  3. Ustaw O. [[StringNextIndex]] na pozycję + cp. [[CodeUnitCount]].
  4. Zwróć CreateIterResultObject (resultString, false).

To, CodeUnitCountco Cię interesuje. Numer ten pochodzi z CodePointAt :

  1. Niech najpierw będzie jednostką kodu w pozycji indeksu w ciągu.
  2. Niech cp będzie punktem kodowym, którego wartością liczbową jest wartość pierwsza.
  3. Jeśli pierwszy nie jest wiodącym zastępczym lub końcowym zastępczym, wówczas

    za. Zwróć rekord { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: false }.

  4. Jeśli pierwszy jest zastępczym zastępcą lub pozycją + 1 = rozmiar, to

    a. Zwróć rekord { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }.

  5. Niech druga będzie jednostką kodu w pozycji indeksu + 1 w ciągu.

  6. Jeśli drugi nie jest końcowym surogatem, to

    za. Zwróć rekord { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }.

  7. Ustaw cp na! UTF16DecodeSurrogatePair (pierwszy, drugi).

  8. Zwróć rekord { [[CodePoint]]: cp, [[CodeUnitCount]]: 2, [[IsUnpairedSurrogate]]: false }.

Tak więc, podczas iteracji po ciągu z Array.from, zwraca CodeUnitCount 2 tylko wtedy, gdy dany znak jest początkiem pary zastępczej. Znaki interpretowane jako pary zastępcze opisano tutaj :

Takie operacje stosują specjalne traktowanie do każdej jednostki kodu o wartości liczbowej w zakresie od 0xD800 do 0xDBFF (zdefiniowanej w standardzie Unicode jako wiodący surogat , lub bardziej formalnie jako jednostka surogatu) i każdej jednostce kodu o wartości liczbowej w zakresie od 0xDC00 do 0xDFFF (zdefiniowanym jako zastępcze zastępcze, lub bardziej formalnie jako jednostka kodu o niskiej zastępczej), stosując następujące reguły ..:

षि nie jest parą zastępczą:

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

Ale 👍bohaterami są:

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

Pierwszym kodem znakowym '👍'jest szesnastkowy kod D83D, który należy do 0xD800 to 0xDBFFwiodących zastępców. W przeciwieństwie do tego, pierwszy kod znakowy 'षि'jest znacznie niższy i nie jest. Więc 'षि'dzieli się, ale '👍'nie robi.

षिskłada się z dwóch oddzielnych postaci: , Devanagari List Ssa , a ि, Devanagari samogłoska Zaloguj I . Gdy są obok siebie w tej kolejności, graficznie łączą się w jedną postać, mimo że składają się z dwóch osobnych postaci.

Natomiast kody znaków mają sens 👍 tylko wtedy, gdy są razem jako pojedynczy glif. Jeśli spróbujesz użyć łańcucha z jednym punktem kodowym bez drugiego, otrzymasz symbol nonsensowny:

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

CertainPerformance
źródło
10
Myślę, że choć w większości poprawna, użyteczna i ze starannie dostarczonymi cytatami, odpowiedź ta nie wyjaśnia jasno kluczowej różnicy między tymi dwoma przypadkami: z punktu widzenia Unicode, षिtak naprawdę są to dwa znaki z odrębnymi punktami kodowymi połączone w jeden glif (jeden abstrakcyjny znak w rozumieniu człowieka). Jest to sprzeczne z 👍emoji, które samo w sobie jest kompletną postacią, mimo że jego punkt kodowy jest na tyle wysoki, że należy go podzielić na parę zastępczą. Uważam, że wyjaśnienie, które mogłoby pomóc w tej (poza tym wartościowej) odpowiedzi wiele.
nosorożec
W szczególności, spółgłoska ष (ṣ) i samogłoska ि (i) graficznie łączą się w sylabę षि (ṣi)
Amadan
@CertainPerformance W „👍” jest tylko jeden punkt kodowy. Sugeruje to, że terminologia w tej odpowiedzi może być niepoprawna.
Ben Aston
13

UTF-16 (kodowanie używane w ciągach znaków w js) używa jednostek 16-bitowych. Zatem każdy kod Unicode, który można przedstawić za pomocą 15 bitów, jest reprezentowany jako jeden punkt kodowy, a wszystko inne jako dwa, znane jako pary zastępcze . Iterator ciągów iteracje nad punktów kodowych.

UTF-16 na Wikipedii

Jonas Wilms
źródło
8

Chodzi o kod za znakami. Niektóre są zakodowane w dwóch bajtach (UTF-16) i są interpretowane Array.fromjako dwa znaki. Muszę sprawdzić listę znaków:

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)));


Dla funkcji wyświetlającej kod szesnastkowy:

JavaScript: ciąg Unicode na szesnastkę

Grégory NEUT
źródło