JavaScript / 39 минут

Строки и Unicode

Научитесь преобразовывать текст, учитывать Unicode и не изменять исходные строки.

В процессе Теория: впереди Практика: впереди Тест: впереди
04 / 22
После этой главы
  • Научитесь преобразовывать текст, учитывать Unicode и не изменять исходные строки.
  • Применить тему в задаче «Строки и Unicode» и объяснить граничные случаи.

Около 9 минут на чтение и разбор примеров. Практика — отдельно, в своём темпе.

Строка как неизменяемое значение

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

Строковые значения неизменяемы. trim, replace и toUpperCase возвращают новые строки. Если вызвать метод и проигнорировать результат, исходное значение не поменяется. let разрешает связать имя с новой строкой, но не делает старую строку изменяемой. Это облегчает рассуждение о преобразованиях: каждое действие можно представить как отдельный шаг от входа к нормализованному результату.

JavaScript
const original = '  JavaScript  ';
const cleaned = original.trim();
console.log(original); // пробелы сохранились
console.log(cleaned.toUpperCase()); // JAVASCRIPT

Поиск, срезы и явные границы

includes отвечает на вопрос о наличии подстроки, startsWith и endsWith проверяют границу, indexOf возвращает позицию либо -1. Нельзя использовать результат indexOf как логическое условие без сравнения: нулевой индекс ложен, хотя совпадение найдено, а -1 истинен. Если нужна только проверка существования, includes обычно выражает намерение понятнее.

slice выделяет часть строки, не изменяя исходную. Начальный индекс включается, конечный не включается. Отрицательный индекс считается от конца. Эти правила важны при работе с расширением файла, коротким описанием и частями пользовательского ввода. Сначала сформулируйте нужные границы на небольшом примере, затем переносите выражение на большой текст.

JavaScript
const file = 'lesson.js';
console.log(file.endsWith('.js')); // true
console.log(file.indexOf('lesson')); // 0
console.log(file.slice(0, 6)); // lesson
console.log(file.slice(-2)); // js

Кодовые единицы, code points и графемы

JavaScript представляет строки последовательностью UTF-16 code units. Свойство length считает именно эти единицы. Для многих букв одна единица соответствует привычному символу, но для части emoji нужны две. Поэтому строка с одним видимым значком может иметь length, равный двум. Индексирование через квадратные скобки также работает с отдельными code units и может выделить половину суррогатной пары.

Строковый итератор перебирает Unicode code points. Его используют for...of, spread и Array.from. Это позволяет не разрезать обычную суррогатную пару, но всё ещё не обещает один элемент на каждый видимый символ: буква с отдельным знаком ударения и составные emoji могут содержать несколько code points. Для пользовательских графем существует Intl.Segmenter, когда он поддерживается целевой средой. Всегда уточняйте, какую именно длину требует задача.

JavaScript
const face = '😀';
console.log(face.length); // 2 UTF-16 единицы
console.log(Array.from(face).length); // 1 code point
console.log(Array.from('е\u0301').length); // 2 code points

Преобразование текста и нормализация

split разбивает строку по разделителю, join объединяет массив строк. Это удобная пара для простых форматов, но split(',') не является полноценным CSV-парсером: в реальном CSV разделитель может встретиться внутри кавычек. Аналогично регулярная замена не заменяет HTML-парсер. Выбирайте инструмент по структуре формата, а не по внешнему сходству строки с примером.

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

JavaScript
const tags = ' JS, TS, Node '.split(',').map(tag => tag.trim());
console.log(tags.join(' · ')); // JS · TS · Node
console.log('е\u0301'.normalize('NFC'));

Текст, HTML и экранирование

Строка, похожая на разметку, остаётся строкой, пока принимающий API не начнёт её интерпретировать. textContent выводит символы как текст, innerHTML разбирает строку как HTML. Когда имя пользователя попадает на страницу, безопасный выбор по умолчанию — текстовый API. Шаблонная строка не проводит автоматическое HTML-экранирование и не проверяет доверенность введённых данных.

Не пытайтесь одним replace обезопасить все способы использования текста. URL, HTML, JSON и SQL имеют разные правила передачи значений. JSON.stringify создаёт JSON-представление, encodeURIComponent кодирует компонент URL, а параметризованный запрос к БД передаёт данные отдельно от текста команды. Контекст назначения определяет нужный механизм.

Практика и граничные случаи

Практическая задача считает code points. Для пустой строки нужен ноль; для обычных букв количество совпадёт с length; для emoji отличие проявится сразу. Составной символ из буквы и отдельного ударения по контракту считается двумя элементами, хотя пользователь может видеть одну букву. Не «улучшайте» условие до графем самостоятельно: правильность решения определяется выбранной единицей.

После решения сравните три способа обхода одной строки: индексы, for...of и Intl.Segmenter. Объясните, почему у них может получиться разное число шагов. Затем напишите нормализацию списка тегов с trim и удалением пустых значений. Проследите, чтобы исходный текст оставался доступным для диагностики, а каждое преобразование имело понятную отдельную цель.

Загружаем редактор…
Следующая темаУсловия и сравнения

Музыка: Reverie — Scott Buckley · CC BY 4.0