- Операторы
- Управляющие инструкции
- JS Объекты
- Array
- Boolean
- Date
- Error
- Function
- Global
- JSON
- Math
- Number
- Object
- RegExp
- String
- Unicode
- Symbol
- Итераторы и генераторы
- Map и WeakMap
- Set и WeakSet
- Локализация
- браузер BOM
- HTML DOM
- События
- HTML Объекты
- Промисы, async/await
- Сетевые запросы
- Бинарные данные и файлы
- Модули
- Классы
- Разное
Юникод
Строки в JavaScript основаны на Юникоде: каждый символ представляет из себя последовательность байтов из 1-4 байтов.
JavaScript позволяет вставить символ в строку, указав его шестнадцатеричный Юникод с помощью одной из этих трех нотаций:
-
\xXX
Вместо
XX
должны быть указаны две шестнадцатеричные цифры со значением от00
доFF
. В этом случае\xXX
– это символ, Юникод которого равенXX
.Поскольку нотация
\xXX
поддерживает только две шестнадцатеричные цифры, ее можно использовать только для первых 256 символов Юникода.Эти 256 символов включают в себя латинский алфавит, большинство основных синтаксических символов и некоторые другие. Например,
"\x7A"
– это то же самое, что"z"
(ЮникодU+007A
).Alert( "\u{20331}" ); // Редкий китайский иероглиф (длинный Юникод) Alert( "\u{1F60D}" ); // Символ улыбающегося лица (ещё один длинный Юникод)
-
\uXXXX
Вместо
XXXX
должны быть указаны ровно 4 шестнадцатеричные цифры со значением от0000
доFFFF
. В этом случае\uXXXX
– это символ, Юникод которого равенXXXX
.Символы со значениями Юникода, превышающими
U+FFFF
, также могут быть представлены с помощью этой нотации, но в таком случае нам придется использовать так называемуюсуррогатную пару
.Alert( "\u00A9" ); // ©, то же самое, что \xA9, используя 4-значную шестнадцатеричную нотацию Alert( "\u044F" ); // я, буква кириллического алфавита Alert( "\u2191" ); // ↑, символ стрелки вверх
-
\u{X…XXXXXX}
Вместо
X…XXXXXX
должно быть шестнадцатеричное значение от 1 до 6 байт от0
до10FFFF
(максимальная точка кода, определенная стандартом Юникод). Эта нотация позволяет нам легко представлять все существующие символы Юникода.Alert( "\u{20331}" ); // Редкий китайский иероглиф (длинный Юникод) Alert( "\u{1F60D}" ); // Символ улыбающегося лица (ещё один длинный Юникод)
Суррогатные пары
Все часто используемые символы имеют 2-байтовые коды (4 шестнадцатеричные цифры). В большинстве европейских языков буквы, цифры и основные унифицированные идеографические наборы CJK (CJK – от китайской, японской и корейской систем письма) имеют 2-байтовое представление.
Изначально JavaScript был основан на кодировке UTF-16, которая предусматривала только 2 байта на один символ. Однако 2 байта допускают только 65536 комбинаций, и этого недостаточно для всех возможных символов Юникода.
Поэтому редкие символы, требующие более 2 байт, кодируются парой 2-байтовых символов, которые называются «суррогатной парой».
Побочным эффектом является то, что длина таких символов равна 2
:
Alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X Alert( '😍'.length ); // 2, FACE WITH TEARS OF JOY Alert( '𠌱'.length ); // 2, редкий китайский иероглиф
Это происходит потому, что суррогатные пары не существовали в то время, когда был создан JavaScript, и поэтому они не обрабатываются языком корректно.
На самом деле в каждой из приведенных строк у нас по одному символу, но свойство length
показывает длину 2
.
Получить такой символ также может быть непросто, поскольку большинство языковых функций рассматривают суррогатные пары как два символа.
Например, здесь мы видим два странных символа в выводе:
Alert( '𝒳'[0] ); // показывает странные символы... Alert( '𝒳'[1] ); // ...части суррогатной пары
Части суррогатной пары не имеют никакого значения друг без друга.
Технически, суррогатные пары также можно определить по их кодам: если символ имеет код в интервале 0xd800...0xdbff
, то он является первой частью суррогатной пары. Следующий символ (вторая часть) должен иметь код в интервале 0xdc00...0xdfff
. Эти интервалы зарезервированы стандартом исключительно для суррогатных пар.
Поэтому для работы с суррогатными парами в JavaScript были добавлены методы String.fromCodePoint
и str.codePointAt
.
По сути, они аналогичны String.fromCharCode и str.charCodeAt, но они правильно обрабатывают суррогатные пары.
Здесь можно увидеть разницу:
// charCodeAt не учитывает суррогатные пары, поэтому он выдает коды для 1-й части 𝒳: Alert( '𝒳'.charCodeAt(0).toString(16) ); // d835 // codePointAt учитывает суррогатные пары Alert( '𝒳'.codePointAt(0).toString(16) ); // 1d4b3, считывает обе части суррогатной пары
При этом, если брать с позиции 1 (а это здесь скорее неверно), то они оба возвращают только 2-ю часть пары:
Alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3 Alert( '𝒳'.codePointAt(1).toString(16) ); // dcb3 // бессмысленная 2-я половина пары
Разделение строки в случайном месте может быть опасным!
Разделив строку в случайном месте, например, с помощью str.slice(0, 4)
, не гарантирует валидность полученного значения. Например:
Alert( 'hi 😍'.slice(0, 4) ); // hi [?]
Здесь видно мусорный символ (первая половина суррогатной пары 😂) в выводе.
Диакритические знаки и нормализация
Во многих языках есть символы, состоящие из основного символа и знака над/под ним.
Например, буква a
может быть основой для этих символов: àáâäãåā
.
Большинство распространенных «составных» символов имеют свой собственный код в таблице Юникода. Но не все, потому что существует слишком большое количество возможных комбинаций.
Для поддержки любых комбинаций стандарт Юникод позволяет использовать несколько Юникодных символов: основной символ, за которым следует один или много символов-«меток», которые «украшают» его.
Например, если за S
следует специальный символ «точка сверху» (код \u0307
), то он отобразится как Ṡ.
Alert( 'S\u0307' ); // Ṡ
Если нужен дополнительный знак над буквой (или под ней) – нет проблем, просто добавляется соответствующий символ.
Например, если добавить символ «точка снизу» (код \u0323
), то получится «S с точками сверху и снизу»: Ṩ
.
Вот, как это будет выглядеть:
Alert( 'S\u0307\u0323' ); // Ṩ
Это обеспечивает большую гибкость, но при этом возникает определенная проблема: два символа могут визуально выглядеть одинаково, но при этом они будут представлены разными комбинациями Юникода.
Например:
let s1 = 'S\u0307\u0323'; // Ṩ, S + точка сверху + точка снизу let s2 = 'S\u0323\u0307'; // Ṩ, S + точка снизу + точка сверху Alert( `s1: ${s1}, s2: ${s2}` ); Alert( s1 == s2 ); // false, хотя символы выглядят одинаково (?!)
Для решения этой проблемы предусмотрен алгоритм «Юникодной нормализации», приводящий каждую строку к единому «нормальному» виду.
Его реализует метод str.normalize().
Alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true
Забавно, но в нашем случае normalize()
«схлопывает» последовательность из трёх символов в один: \u1e68
— S с двумя точками.
Alert( "S\u0307\u0323".normalize().length ); // 1 Alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true
В действительности это не всегда так. Причина в том, что символ Ṩ
является «достаточно распространенным», поэтому создатели стандарта Юникод включили его в основную таблицу и присвоили ему код.
О правилах и вариантах нормализации можно узнать в дополнении к стандарту Юникод: Unicode Normalization Forms.