Ха, почему "𠮷𠮷𠮷".length !== 3

интервью внешний интерфейс JavaScript
Ха, почему "𠮷𠮷𠮷".length !== 3

В процессе разработки я иногда сталкивался с проблемами, связанными с кодировкой, Unicode и Emoji, и обнаружил, что не полностью усвоил базовые знания в этой области. Поэтому после некоторого поиска и изучения я организовал несколько простых для понимания статей, чтобы поделиться ими.

Несколько статей из серии [Hardcore Foundation], о которых заинтересованные друзья могут узнать:

Я не знаю, сталкивались ли вы с такими сомнениями, в требовании длины проверки формы вы найдете разные символыlengthМожет различаться по размеру. Например, длина «𠮷» в названии равна 2 (обратите внимание, что 📢 — это не китайский иероглиф!).

'吉'.length
// 1

'𠮷'.length
// 2

'❤'.length
// 1

'💩'.length
// 2

Чтобы объяснить эту проблему, начнем с кодировки UTF-16.

UTF-16

отECMAScript® 2015Как видно из спецификации, строки ECMAScript кодируются в UTF-16.

Фиксированные и неопределенные:Наименьшая кодовая единица в UTF-16 составляет два байта, что является фиксированным, даже если первый байт может быть равен 0. Может случиться так, что для символов базовой плоскости (BMP) требуется всего два байта, указывающих диапазонU+0000 ~ U+FFFF, а для дополнительной плоскости требуется четыре байтаU+010000~U+10FFFF.

В предыдущей статье мы представили детали кодировки utf-8 и узнали, что кодировка utf-8 занимает от 1 до 4 байтов, а для использования utf-16 требуется 2 или 4 байта. Давайте посмотрим, как кодируется utf-16.

Логика кодирования UTF-16

Кодировка UTF-16 очень проста для данной кодовой точки Unicode cp (CodePoint — это уникальный номер этого символа в Unicode):

  1. Если кодовая точка меньше или равнаU+FFFF(то есть все символы базовой плоскости), не нужно обрабатывать, использовать напрямую.
  2. В противном случае он будет разделен на две части.((cp – 65536) / 1024) + 0xD800,((cp – 65536) % 1024) + 0xDC00хранить.

Стандарт Unicode указывает, что значение U+D800...U+DFFF не соответствует ни одному символу, поэтому его можно использовать для разметки.

В качестве конкретного примера: кодовая точка символа AU+0041, который может быть непосредственно представлен символом.

'\u0041'
// -> A

A === '\u0041'
// -> true

в Javascript\uЭкранирующий символ, представляющий Юникод, за которым следует шестнадцатеричное число.

И кодовая точка символа 💩U+1f4a9, символы в дополнительной плоскости, два символа получаются по формуле 👆55357, 56489Два числа представлены в шестнадцатеричном виде какd83d, dca9, который объединяет два результата кодирования в суррогатную пару.

'\ud83d\udca9'
// -> '💩'

'💩' === '\ud83d\udca9'
// -> true

Поскольку строки Javascript используютutf-16кодирование, поэтому суррогат может быть правильно\ud83d\udca9декодировать, чтобы получить кодовую точкуU+1f4a9.

также можно использовать\u + {}, за фигурными скобками непосредственно следует кодовая точка для представления символа. Они выглядят по-разному, но выражают один и тот же результат.

'\u0041' === '\u{41}'
// -> true

'\ud83d\udca9' === '\u{1f4a9}'
// -> true

Вы можете открыть панель консоли Dev Tool и запустить код, чтобы проверить результаты.

Так почему же возникает проблема с оценкой длины?

Чтобы ответить на этот вопрос, вы можете продолжить просмотрСпецификация, в котором упоминается: когда операции ECMAScript интерпретируют строковые значения, каждыйэлементинтерпретируются какне замужемКодовая единица UTF-16.

Where ECMAScript operations interpret String values, each element is interpreted as a single UTF-16 code unit.

Таким образом, такие символы, как 💩, на самом деле занимают две кодовые единицы UTF-16, то есть два элемента.lengthсобственность2. (Это то же самое, что использовать JS в началеUSC-2Связано с кодированием, я думал65536характера хватает на все нужды)

Но для обычных пользователей это совершенно непонятно.Почему заполняется только одна '𠮷', а программа подсказывает, что длина в два символа занята. Как правильно определить длину символов Юникода?

я здесьAntdФорма, используемая формойasync-validatorСледующий абзац можно увидеть в упаковкекод

const spRegexp = /[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]/g;

if (str) {
  val = value.replace(spRegexp, '_').length;
}

Когда необходимо оценить длину строки, все символы в диапазоне кодовых точек в дополнительной плоскости будут заменены символами подчеркивания, чтобы оценка длины соответствовала фактическому отображению! ! !

Поддержка Юникода в ES6.

lengthПроблема атрибутов в основном в том, что когда язык JS изначально разрабатывался, он не учитывал, что будет так много символов, и думал, что двух байт будет достаточно. Так что не толькоlength, некоторые общие операции над строками находятся вUnicodeПоддержка также покажетаномальный.

Следующий контент представит некоторые ненормальныеAPIИ вES6как правильно решать эти вопросы.

for vs for of

Например, используйтеforВыведите строку в цикле, строка будет проходиться по каждому «элементу», понятному JS, а символы вспомогательной плоскости будут распознаваться как два «элемента», поэтому появляются «искаженные символы».

var str = '👻yo𠮷'
for (var i = 0; i < str.length; i ++) {
  console.log(str[i])
}

// -> �
// -> �
// -> y
// -> o
// -> �
// -> �

при использовании ES6for ofГрамматика не делает.

var str = '👻yo𠮷'
for (const char of str) {
  console.log(char)
}

// -> 👻
// -> y
// -> o
// -> 𠮷

Синтаксис спреда

Как упоминалось ранее, использование регулярных выражений для замены символов вспомогательной плоскости для подсчета длины символов. Тот же эффект может быть достигнут с использованием синтаксиса расширения.

[...'💩'].length
// -> 1

Та же проблема существует с методами slice, split, substr и т.д.

регулярное выражение ты

ES6 также добавляет новую функцию для символов Unicode.uДескриптор.

/^.$/.test('👻')
// -> false

/^.$/u.test('👻')
// -> true

charCodeAt/codePointAt

Для строк мы также используемcharCodeAtЧтобы получить Code Point, это применимо к символам плоскости BMP, но если символы являются вспомогательными символами плоскостиcharCodeAtВозвращаемым результатом будет только номер первой пары символов после кодирования.

'羽'.charCodeAt(0)
// -> 32701
'羽'.codePointAt(0)
// -> 32701

'😸'.charCodeAt(0)
// -> 55357
'😸'.codePointAt(0)
// -> 128568

при использованииcodePointAtтогда символ распознается правильно и возвращается правильная кодовая точка.

String.prototype.normalize()

Поскольку в JS под строкой понимается последовательность двухбайтовых элементов кода, о равенстве судят по значению последовательности. Таким образом, могут быть некоторые строки, которые выглядят точно так же, но результат решения о равенстве строк действительноfalse.

'café' === 'café'
// -> false

Первый в приведенном выше кодеcaféТам естьcafeплюс фонетический символ с отступом\u0301составлено, а второеcaféпоcaf + éсостоит из символов. Таким образом, хотя они выглядят одинаково, но кодовые точки разные, поэтому результат оценки равенства JS равенfalse.

'cafe\u0301'
// -> 'café'

'cafe\u0301'.length
// -> 5

'café'.length
// -> 4

Чтобы правильно идентифицировать этот тип оценки строки с разными кодовыми точками, но с той же семантикой, ES6 добавилString.prototype.normalizeметод.

'cafe\u0301'.normalize() === 'café'.normalize()
// -> true

'cafe\u0301'.normalize().length
// -> 4

Суммировать

Эта статья в основном представляет собой мои недавние учебные заметки для повторного изучения кодирования. Из-за спешки времени и ограниченного уровня в статье должно быть много неточных описаний и даже неправильного содержания. Если вы что-то найдете, пожалуйста, укажите на это . ❤️

Справочная статья