Юникод: флаг "u"

Применение флага юникода /.../u позволяет корректно работать с суррогатными парами.

О том, что такое суррогатные пары, подробно рассказывалось ранее в главе Строки.

Сейчас же давайте вкратце вспомним, что они из себя представляют. Обычно символы кодируются с помощью 2 байтов, что позволяет закодировать максимум 65536 символов. Однако в разных языках по всему миру используется большее количество символов.

Таким образом, некоторые редкие символы кодируются с помощью 4 байтов, например 𝒳 (математический X) или 😄 (смайлик).

В таблице ниже приведены для сравнения юникоды нескольких символов:

Символ Юникод Байты
a 0x0061 2
0x2248 2
𝒳 0x1d4b3 4
𝒴 0x1d4b4 4
😄 0x1f604 4

Таким образом, символы типа a и занимают по 2 байта, а более редкие – по 4.

Юникод сделан так, что закодированные с помощью 4 байтов символы воспринимаются и обрабатываются как единое целое.

В прошлом JavaScript не знал об этом, и многие строковые функции всё ещё могут работать некорректно. Например, свойство length считает, что здесь два символа:

alert('😄'.length); // 2
alert('𝒳'.length); // 2

…Но мы видим, что только один, верно? Суть в том, что свойство length воспринимает 4 байта, реально занимаемые символом, как два символа по 2 байта. Это вовсе не верно, потому что эти два символа должны восприниматься как единое целое (так называемая «суррогатная пара»).

Регулярные выражения также обычно воспринимают 4-байтные «длинные символы» как пары 2-байтных.

Это приводит к странным результатам. Например, давайте попробуем найти шаблон [𝒳𝒴] в строке 𝒳:

alert( '𝒳'.match(/[𝒳𝒴]/) ); // показывает странный результат (поиск был произведён неправильно, и вернулась только половина символа)

Результат неверный, потому что по умолчанию регулярные выражения не работают с суррогатными парами.

Таким образом, движок регулярных выражений думает, что [𝒳𝒴] – это не два, а четыре символа:

  1. левая половина от 𝒳 (1),
  2. правая половина от 𝒳 (2),
  3. левая половина от 𝒴 (3),
  4. правая половина от 𝒴 (4).

Мы даже можем вывести их:

for(let i=0; i<'𝒳𝒴'.length; i++) {
  alert('𝒳𝒴'.charCodeAt(i)); // 55349, 56499, 55349, 56500
};

То есть в нашем примере выше ищется и выводится только левая половина от 𝒳.

Другими словами, поиск работает примерно как '12'.match(/[1234]/): только первый символ возвращается.

Флаг «u»

Специальный флаг /.../u исправляет ситуацию.

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

alert( '𝒳'.match(/[𝒳𝒴]/u) ); // 𝒳

Давайте рассмотрим ещё один пример.

Если в коде ниже мы забудем поставить флаг u, а в строке будут суррогатные пары, то мы получим ошибку:

'𝒳'.match(/[𝒳-𝒴]/); // SyntaxError: invalid range in character class

Обычно движок регулярных выражений понимает запись [a-z] как "диапазон символов с кодами между кодами символов a и z.

Но без флага u суррогатные пары воспринимаются как просто «пары независимых символов», то есть [𝒳-𝒴] превращается в [<55349><56499>-<55349><56500>] (происходит замена каждого члена суррогатной пары его кодом). Сейчас явно видно, что указанный диапазон 56499-55349 – неправильный, так как начальное значение слева должно быть меньше, чем конечное значение справа.

Использование флага u исправляет ситуацию:

alert( '𝒴'.match(/[𝒳-𝒵]/u) ); // 𝒴
Карта учебника

Комментарии

перед тем как писать…
  • Если вам кажется, что в статье что-то не так - вместо комментария напишите на GitHub.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.