Welche Zeichen werden mit Array.from gruppiert?

38

Ich habe mit JS herumgespielt und kann nicht herausfinden, wie JS entscheidet, welche Elemente bei der Verwendung zum erstellten Array hinzugefügt werden sollen Array.from(). Das folgende Emoji 👍 hat beispielsweise eine length2, da es aus zwei Codepunkten besteht. Array.from()Diese beiden Codepunkte werden jedoch als eins behandelt, wodurch ein Array mit einem Element erhalten wird:

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

Einige andere Zeichen haben jedoch auch zwei Codepunkte wie dieses Zeichen षि(hat auch einen .lengthvon 2). Jedoch Array.fromnicht „Gruppe“ dieses Zeichen und stattdessen erzeugt zwei Elemente:

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

Meine Frage ist: Was bestimmt, ob das Zeichen aufgeteilt wird (wie in Beispiel zwei) oder als ein einzelnes Element behandelt wird (wie in Beispiel eins), wenn das Zeichen aus zwei Codepunkten besteht?

Shnick
quelle
5
Schauen Sie sich UTF-16-Ersatzpaare an ...
Jonas Wilms
1
Ich habe Bedenken hinsichtlich der MDN-Polyfüllung von Array.from, die sich anders verhält: -s
Ele
1
@Ele berücksichtigt nur Objekte mit length. Iteratoren oder Setfunktionieren sogar nicht damit
Adiga

Antworten:

26

Array.fromZuerst wird versucht, den Iterator des Arguments aufzurufen, falls vorhanden, und Zeichenfolgen haben Iteratoren. Daher wird er aufgerufen String.prototype[Symbol.iterator]. Schauen wir uns also an, wie die Prototypmethode funktioniert. Es ist in der Spezifikation hier beschrieben :

  1. Lass O sein? RequireObjectCoercible (dieser Wert).
  2. Lass uns sein ? ToString (O).
  3. Geben Sie CreateStringIterator (S) zurück.

Wenn CreateStringIteratorSie nach oben schauen, gelangen Sie schließlich zu 21.1.5.2.1 %StringIteratorPrototype%.next ( ):

  1. Lass cp sein! CodePointAt (s, Position).
  2. Sei resultString der String-Wert, der cp enthält. [[CodeUnitCount]] aufeinanderfolgende Codeeinheiten von s beginnend mit der Codeeinheit an der Indexposition.
  3. Setzen Sie O. [[StringNextIndex]] auf Position + cp. [[CodeUnitCount]].
  4. Rückgabe CreateIterResultObject (resultString, false).

Das CodeUnitCountist, woran Sie interessiert sind. Diese Nummer stammt von CodePointAt :

  1. Sei zunächst die Codeeinheit an der Indexposition innerhalb der Zeichenfolge.
  2. Sei cp der Codepunkt, dessen numerischer Wert der von first ist.
  3. Wenn zuerst kein führender oder nachfolgender Ersatz ist, dann

    ein. Geben Sie den Datensatz zurück { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: false }.

  4. Wenn zuerst ein nachfolgender Ersatz oder eine Position + 1 = Größe ist, dann

    a.Rückgabe der Aufzeichnung { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }.

  5. Als zweites sei die Codeeinheit an der Indexposition + 1 innerhalb der Zeichenfolge.

  6. Wenn der zweite kein nachfolgender Ersatz ist, dann

    ein. Geben Sie den Datensatz zurück { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }.

  7. Setze cp auf! UTF16DecodeSurrogatePair (erste, zweite).

  8. Geben Sie den Datensatz zurück { [[CodePoint]]: cp, [[CodeUnitCount]]: 2, [[IsUnpairedSurrogate]]: false }.

Wenn Sie also eine Zeichenfolge mit Array.fromdurchlaufen, wird nur dann ein CodeUnitCount von 2 zurückgegeben, wenn das betreffende Zeichen der Anfang eines Ersatzpaars ist. Zeichen, die als Ersatzpaare interpretiert werden, werden hier beschrieben :

Solche Operationen wenden eine Sonderbehandlung für jede Codeeinheit mit einem numerischen Wert im Inklusivbereich 0xD800 bis 0xDBFF (definiert durch den Unicode-Standard als führender Ersatz oder formeller als Codeeinheit mit hohem Ersatz ) und jede Codeeinheit mit einem numerischen Wert an im Inklusivbereich 0xDC00 bis 0xDFFF (definiert als nachfolgender Ersatz oder formeller als Codeeinheit mit niedrigem Ersatz) unter Verwendung der folgenden Regeln:

षि ist kein Ersatzpaar:

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

Aber 👍die Charaktere sind:

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

Der erste Zeichencode von '👍'ist in hexadezimaler Form D83D, was im Bereich 0xD800 to 0xDBFFder führenden Surrogate liegt. Im Gegensatz dazu ist der erste Zeichencode von 'षि'viel niedriger und nicht. Das 'षि'wird also aufgeteilt, '👍'tut es aber nicht.

षिbesteht aus zwei getrennten Zeichen: , Devanagari Brief Ssa und ि, Devanagari Vowel Zeichen I . Wenn sie in dieser Reihenfolge nebeneinander stehen, werden sie visuell grafisch zu einem einzigen Zeichen kombiniert, obwohl sie aus zwei separaten Zeichen bestehen.

Im Gegensatz dazu sind die Zeichencodes von 👍 nur dann sinnvoll, wenn sie als einzelne Glyphe zusammengefasst sind. Wenn Sie versuchen, eine Zeichenfolge mit einem der Codepunkte ohne den anderen zu verwenden, erhalten Sie ein Unsinnssymbol:

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

Bestimmte Leistung
quelle
10
Ich denke, dass diese Antwort, obwohl sie größtenteils korrekt, nützlich und mit sorgfältig bereitgestellten Zitaten versehen ist, den Hauptunterschied zwischen den beiden Fällen nicht klar erklärt: Aus Unicode-Sicht षिhandelt es sich tatsächlich um zwei Zeichen mit unterschiedlichen Codepunkten, die zu einem einzigen kombiniert werden Glyphe (ein abstraktes Zeichen, wie es vom Menschen verstanden wird). Dies steht im Gegensatz zum 👍Emoji, das an und für sich ein vollständiges Zeichen ist, obwohl sein Codepunkt hoch genug ist, dass es in ein Ersatzpaar aufgeteilt werden muss. Ich glaube zu klären, dass diese (ansonsten wertvolle) Antwort viel helfen könnte.
Nashorn
Insbesondere der Konsonant ष (ṣ) und der Vokal ि (i) verbinden sich grafisch zu der Silbe षि (ṣi)
Amadan
@CertainPerformance Es gibt nur einen Codepunkt in "👍". Dies deutet darauf hin, dass die Terminologie in dieser Antwort möglicherweise falsch ist.
Ben Aston
13

UTF-16 (die für Zeichenfolgen in js verwendete Codierung) verwendet 16-Bit-Einheiten. Jeder Unicode, der mit 15 Bit dargestellt werden kann, wird als ein Codepunkt dargestellt, alles andere als zwei, sogenannte Ersatzpaare . Der Iterator von Zeichenfolgen iteriert über Codepunkte.

UTF-16 auf Wikipedia

Jonas Wilms
quelle
8

Es geht nur um den Code hinter den Zeichen. Einige sind in zwei Bytes (UTF-16) codiert und werden Array.fromals zwei Zeichen interpretiert . Muss die Liste der Charaktere überprüfen:

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


Für die Funktion, die den Hex-Code anzeigt:

Javascript: Unicode-Zeichenfolge zu hex

Grégory NEUT
quelle