В этой статье я хочу немного выйти за рамки традиционного представления о том, что «Строка — это последовательность символов». Мы посмотрим, что скрывается за этим мышлением, и попытаемся понять возможную реализацию строк в JavaScript.

Что такое строка ECMAScript?

Спецификация определяет тип String следующим образом:

Строковый тип — это набор всех упорядоченных последовательностей из нуля или более 16-битных целых чисел без знака («элементов») до максимальной длины 2⁵³–1 элемента.

Это довольно короткое, но очень объемное определение. Выделю только два момента:

  • Последовательность (55358, 56736) имеет тип String.
  • Спецификация отделяет внутреннее представление (16-битные целые числа без знака) от внешнего (фактические символы).

Итак, как же последовательность (55358, 56736) становится вездесущим символом 🦠?

Как интерпретируются строки ECMAScript?

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

Как и спецификация ECMAScript, кодировка UTF-16 использует 16-битные целые числа без знака для кодирования символов. Целые числа, начинающиеся с 55296 (0xD800 в шестнадцатеричном формате) до 56319 (0xDBFF), называются «начальным суррогатом», а целые числа, начинающиеся с 56320 (0xDC00) до 57343 (0xDFFF), называются «конечным суррогатом». Полная пара (ведущий суррогат, замыкающий суррогат) называется «суррогатной парой».

Теперь мы можем понять нашу последовательность (55358, 56736) или (0xD83E, 0xDDA0) в шестнадцатеричном формате. Как мы видим, это суррогатная пара. Спецификация ECMAScript предлагает использовать следующую формулу для получения окончательного символа 🦠:

(c₁–0xD800) × 0x400 + (c₂–0xDC00) + 0x10000

где (c₁, c₂) = (начальный суррогат, замыкающий суррогат).

Подставляя наши значения, получаем:

(0xD83E — 0xD800) * 0x400 + (0xDDA0–0xDC00) + 0x10000 = 0x1F9A0.

Действительно, в таблице символов UTF-16 есть символ с таким же шестнадцатеричным кодом.

Как ECMAScript интерпретирует последовательность чисел

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

Я предполагаю, что константа charTable — это карта пар ‹целочисленный код, символ›, а константа sequence — это начальная последовательность 16-битных целых чисел без знака.

Я намеренно не обрабатывал искаженный ввод, чтобы сделать код простым. Все необходимые чеки вы можете выписать дома, если хотите 😉

Когда длина не так очевидна

Проделаем обратные операции и найдем суррогатную пару для символа 🦠.

Как мы видели, для кодирования символа требуется два целых числа. В ECMAScript это означает, что длина символа также равна двум. Мы можем использовать функцию codePointAt, чтобы получить фактическое целое число, используемое на внутреннем уровне.

Собираем все вместе

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

Домашнее задание

Что такое строка, внутреннее представление которой соответствует следующей последовательности: (1055, 1088, 1080, 1074, 1077, 1090)?