Внутренняя кодировка строк в JavaScript – это UTF‑16, то есть под каждый символ отводится ровно два байта.
Но под всевозможные символы всех языков мира 2 байт не хватает. Поэтому бывает так, что одному символу языка соответствует два юникодных символа (итого 4 байта). Такое сочетание называют «суррогатной парой».
Самый частый пример суррогатной пары, который можно встретить в литературе – это китайские иероглифы.
Заметим, однако, что не всякий китайский иероглиф – суррогатная пара. Существенная часть «основного» юникод‑диапазона как раз отдана под китайский язык, поэтому некоторые иероглифы – которые в неё «влезли» – представляются одним юникод‑символом, а те, которые не поместились (реже используемые) – двумя.
Например:
alert( '我'.length ); // 1 alert( ' '.length ); // 2
В тексте выше для первого иероглифа есть отдельный юникод‑символ, и поэтому длина строки 1 , а для второго используется суррогатная пара. Соответственно, длина – 2 .
Китайскими иероглифами суррогатные пары, естественно, не ограничиваются.
Ими представлены редкие математические символы, а также некоторые символы для эмоций, к примеру:
alert( '𝑌'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X alert( ' '.length ); // 2, FACE WITH TEARS OF JOY
В современный JavaScript добавлены методы String.fromCodePoint и str.codePointAt – аналоги String.fromCharCode и str.charCodeAt , корректно работающие с суррогатными парами.
Например, charCodeAt считает суррогатную пару двумя разными символами и возвращает код каждой:
// как будто в строке два разных символа (на самом деле один)
alert( '𝑌'.charCodeAt(0) + ' ' + '𝑌'.charCodeAt(1) ); // 55349 56499
…В то время как codePointAt возвращает его Unicode‑код суррогатной пары правильно:
// один символ с "длинным" (более 2 байт) unicode‐кодом alert( '𝑌'.codePointAt(0) ); // 119987
Метод String.fromCodePoint(code) корректно создаёт строку из «длинного кода», в отличие от старого String.fromCharCode(code) . Например:
// Правильно
alert( String.fromCodePoint(119987) ); // 𝑌
// Неверно!
alert( String.fromCharCode(119987) ); // 풳
Более старый метод fromCharCode в последней строке дал неверный результат, так как он берёт только первые два байта от числа 119987 и создаёт символ из них, а остальные отбрасывает.
\u{длинный код}
Есть и ещё синтаксическое улучшение для больших Unicode‑кодов.
В JavaScript‑строках давно можно вставлять символы по Unicode‑коду, вот так:
alert( "\u2033" ); // ″, символ двойного штриха
Синтаксис: \uNNNN , где NNNN – четырёхзначный шестнадцатиричный код, причём он должен быть ровно четырёхзначным.
«Лишние» цифры уже не войдут в код, например:
alert( "\u20331" ); // Два символа: символ двойного штриха ″, а затем 1
Чтобы вводить более длинные коды символов, добавили запись \u{NNNNNNNN} , где NNNNNNNN – максимально восьмизначный (но можно и меньше цифр) код.
Например:
alert( "\u{20331}" ); // ‰, китайский иероглиф с этим кодом
Unicode‑нормализация
Во многих языках есть символы, которые получаются как сочетание основного символа и какого‑то значка над ним или под ним.
Например, на основе обычного символа a существуют символы: àáâäãåā . Самые часто встречающиеся подобные сочетания имеют отдельный юникодный код. Но отнюдь не все.
Для генерации произвольных сочетаний используются несколько юникодных символов: основа и один или несколько значков. Например, если после символа S идёт символ «точка сверху» (код \u0307 ), то показано это будет как «S с точкой сверху» Ṡ . Если нужен ещё значок над той же буквой (или под ней) – без проблем. Просто добавляем соответствующий символ.
К примеру, если добавить символ «точка снизу» (код \u0323 ), то будет «S с двумя точками сверху и снизу» Ṩ . Пример этого символа в JavaScript‑строке:
alert("S\u0307\u0323"); // Ṩ
Такая возможность добавить произвольной букве нужные значки, с одной стороны, необходима, а с другой стороны – возникает проблемка: можно представить одинаковый с точки зрения визуального отображения и интерпретации символ – разными сочетаниями Unicode‑кодов.
Вот пример:
alert("S\u0307\u0323"); // Ṩ alert("S\u0323\u0307"); // Ṩ
alert( "S\u0307\u0323" == "S\u0323\u0307" ); // false
В первой строке после основы S идёт сначала значок «верхняя точка», а потом – нижняя, во второй – наоборот. По кодам строки не равны друг другу. Но символ задают один и тот же.
С целью разрешить эту ситуацию, существует юникодная нормализация, при которой строки приводятся к единому, «нормальному», виду. В современном JavaScript это делает метод 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 .
Do'stlaringiz bilan baham: |