Какие символы сгруппированы с Array.from? - PullRequest
38 голосов
/ 04 февраля 2020

Я играл с JS и не могу понять, как JS решает, какие элементы добавить в созданный массив при использовании Array.from(). Например, следующий emoji ? имеет length из 2, так как он состоит из двух кодовых точек, но Array.from() обрабатывает эти две кодовые точки как одну, давая массив с одним элементом:

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

Однако некоторые другие символы также имеют две кодовые точки, например, этот символ षि (также имеет .length, равный 2). Однако Array.from не «группирует» этот символ и вместо этого создает два элемента:

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

Мой вопрос: что определяет, будет ли символ разбиваться (как в примере два) или рассматриваться как один отдельный элемент (как в примере один), когда символ состоит из две кодовые точки?

Ответы [ 3 ]

26 голосов
/ 04 февраля 2020

Array.from сначала пытается вызвать итератор аргумента, если он есть, и в строках есть итераторы, поэтому он вызывает String.prototype[Symbol.iterator], поэтому давайте посмотрим, как работает метод-прототип. Это описано в спецификации здесь :

  1. Пусть O будет? RequireObjectCoercible (это значение).
  2. Пусть S будет? ToString (O).
  3. Возврат CreateStringIterator (S).

Поиск CreateStringIterator в конечном итоге приведет вас к 21.1.5.2.1 %StringIteratorPrototype%.next ( ), что делает :

Пусть cp будет! CodePointAt (s, position). Пусть resultString будет строковым значением, содержащим cp. [[CodeUnitCount]] последовательные кодовые единицы от s, начиная с кодовой единицы в позиции индекса. Set O. [[StringNextIndex]] в положение + cp. [[CodeUnitCount]]. Возврат CreateIterResultObject (resultString, false).

CodeUnitCount - это то, что вы ' Вы заинтересованы в этом. Этот номер прибывает из CodePointAt :

Пусть first будет кодовой единицей в позиции индекса в строке. Пусть cp будет кодовой точкой, чье значение numberri c соответствует значению first.

Если first is не ведущий суррогат или отставший суррогат, тогда

a. Вернуть запись { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: false }.

Если первым является конечный суррогат или позиция + 1 = размер, то

a. Возврат записи { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }.

Пусть секунда - это кодовая единица в позиции индекса + 1 в строке.

Если секунда не является последним суррогатом, то

а. Верните запись { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }.

Установите для cp значение! UTF16DecodeSurrogatePair (first, second).

Вернуть запись { [[CodePoint]]: cp, [[CodeUnitCount]]: 2, [[IsUnpairedSurrogate]]: false }.

Итак, при итерации по строке с Array.from, он возвращает CodeUnitCount, равный 2, только когда рассматриваемый символ является началом суррогатной пары. Символы, которые интерпретируются как суррогатные пары, описаны здесь :

Такие операции применяют особый режим к каждой единице кода со значением цифры c в включенном диапазоне 0xD800 0xDBFF (определяется стандартом Unicode как ведущий суррогат или, более формально, как высокосуррогатная кодовая единица) и каждая кодовая единица со значением цифры c в включающем диапазоне от 0xDC00 до 0xDFFF (определяется как конечный суррогат или, более формально, как единица кода с низким суррогатным кодом) с использованием следующих правил ..:

षि не является суррогатной парой:

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

Но символы ?:

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

Первый символьный код '?' в шестнадцатеричном формате представляет собой D83D, который находится в диапазоне 0xD800 to 0xDBFF ведущих суррогатов. Напротив, первый символьный код 'षि' намного ниже и не является. Таким образом, 'षि' разделяется, а '?' - нет.

षि состоит из двух отдельных символов: , Буква Деванагари Ssa и ि, Деванагари гласный знак I . Находясь рядом друг с другом в этом порядке, они визуально графически объединяются в один символ, несмотря на то, что состоят из двух отдельных символов.

В отличие от кодов символов ? только имеет смысл, когда вместе, как один глиф. Если вы попытаетесь использовать строку с одной кодовой точкой без другой, вы получите бессмысленный символ:

console.log('?'[0]);
console.log('?'[1]);
13 голосов
/ 04 февраля 2020

UTF-16 (кодировка, используемая для строк в js) использует 16-битные единицы. Таким образом, каждый Unicode, который может быть представлен с использованием 15 бит, представлен как одна кодовая точка, а все остальное как две, известные как суррогатные пары . Итератор строк выполняет итерации по кодам.

UTF-16 в Википедии

8 голосов
/ 04 февраля 2020

Все дело в коде символов. Некоторые кодируются в двух байтах (UTF-16) и интерпретируются Array.from как два символа. Должен проверить список символов:

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

Для функции, которая отображает шестнадцатеричный код:

Javascript: строка Unicode в шестнадцатеричном виде

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...