Які символи групуються з Array.from?


38

Я граю з JS і не можу зрозуміти, як JS вирішує, які елементи додати до створеного масиву при використанні Array.from(). Наприклад, наступний смайлик 👍 має 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 ...
Jonas Wilms


1
У мене є стурбованість полігоном MDN від Array.from, який має іншу поведінку: -s
Ele

1
@Ele він розглядає лише об'єкти з length. Ітератори або навіть Setне працюють з цим
адига

Відповіді:


26

Array.fromспочатку намагається викликати ітератор аргументу, якщо він має його, а рядки мають ітератори, тому він викликає String.prototype[Symbol.iterator], так що давайте подивимось, як працює метод прототипу. Це описано в специфікації тут :

  1. Нехай О буде? RequireObjectCoercible (це значення).
  2. Нехай S бути? ToString (O).
  3. Повернути CreateStringIterator (S).

Дивлячись в CreateStringIteratorкінці кінців приведе вас 21.1.5.2.1 %StringIteratorPrototype%.next ( ), що робить:

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

Це CodeUnitCountте, що вас цікавить. Це число походить від CodePointAt :

  1. Дозвольте спочатку бути одиницею коду в позиції індексу в рядку.
  2. Нехай cp - кодова точка, числове значення якої є першою.
  3. Якщо спочатку не є провідним сурогатом чи заднім сурогатом, то

    а. Повернути запис { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: false }.

  4. Якщо спочатку є сурогат чи позиція + 1 = розмір, то

    a. Поверніть Запис { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }.

  5. Нехай другим є одиниця коду в позиції індексу + 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-бітні одиниці. Отже, кожен унікод, який можна представити за допомогою 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.