Какие символы сгруппированы с Array.from?


38

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

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

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

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

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


5
Взгляните на суррогатные пары UTF-16 ...
Джонас Уилмс


1
У меня есть беспокойство по поводу polyfill MDN о Array.from, который имеет другое поведение: -s
Эле

1
@Ele рассматривает только объекты с length. Итераторы или даже Setне работают с этим
Адига

Ответы:


26

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

  1. Позволь О быть? RequireObjectCoercible (это значение).
  2. Давай будем ? ToString (О).
  3. Вернуть CreateStringIterator (S).

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

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

Это CodeUnitCountто, что вас интересует. Этот номер взят из CodePointAt :

  1. Пусть first будет единица кода в позиции индекса в строке.
  2. Пусть cp будет кодовой точкой, числовое значение которой равно значению first.
  3. Если первый не является ведущим суррогатом или последним суррогатом, то

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

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

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

  5. Пусть second будет единица кода в позиции индекса + 1 в строке.

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

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

  7. Установите cp в! UTF16DecodeSurrogatePair (первый, второй).

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

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

Такие операции применяют специальный режим к каждой единице кода с числовым значением в диапазоне от 0xD800 до 0xDBFF (определяемом стандартом Unicode как ведущая суррогатная или более формально как единица кода с высокой степенью суррогата) и к каждой единице кода с числовым значением в инклюзивном диапазоне от 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 , и ि, деванагари Vowel Вход I . Находясь рядом друг с другом в этом порядке, они визуально графически объединяются в один символ, несмотря на то, что состоят из двух отдельных символов.

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

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


10
Я думаю, что этот ответ, хотя и в основном правильный, полезный и с тщательно предоставленными цитатами, не дает четкого объяснения ключевой разницы между этими двумя случаями: с точки зрения Unicode, षिна самом деле это два символа с различными кодовыми точками, объединенными в одну глиф (один абстрактный символ, понимаемый людьми). Это контрастирует с 👍эмодзи, который сам по себе является полным символом, хотя его кодовая точка достаточно высока, чтобы его можно было разделить на суррогатную пару. Я верю, что разъяснение может помочь этому (в противном случае ценному) ответу.
носорог

В частности, согласный ष (ṣ) и гласный ि (i) графически объединяются в слог षि (ṣi)
Амадан

@CertainPerformance В «👍» есть только одна кодовая точка. Это говорит о том, что терминология в этом ответе может быть неверной.
Бен Астон

13

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

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


8

Все дело в коде символов. Некоторые кодируются двумя байтами (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 строка в шестнадцатеричный

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.