Перейти к содержимому
Все статьи
РазработкаХостинг и серверы

Лимит символов с эмодзи: почему length считает иначе

·27 октября 2026 г.· 6 мин чтения
Сколько будет стоить ваш проект?Рассчитать стоимость

JavaScript length считает единицы UTF-16, поэтому число может отличаться от количества символов, которые видит пользователь. Для лимита по графемам используйте согласованную сегментацию, например Intl.Segmenter с granularity: «grapheme», и такое же правило при сохранении. Отдельно ограничивайте технический объём данных: графемы, кодовые точки и байты отвечают на разные вопросы.

Слово «символ» нужно определить в задании

Фраза «разрешено двадцать символов» кажется однозначной, пока в форме нет эмодзи и составных букв. Пользователь считает видимый знак, браузерный код получает длину строки, сервер может проверять другую единицу. В результате счётчик разрешает отправку, но сохранение отклоняет текст без понятного объяснения.

Запишите, что ограничивает поле: видимую длину подписи, допустимый объём сообщения для внешнего API или размер закодированных данных. У каждого назначения своя единица. Не стоит менять её только в счётчике, оставляя прежнюю проверку на сервере: интерфейс станет дружелюбнее, но противоречие сохранится.

Три счётчика одной строки

MDN описывает String length как число кодовых единиц UTF-16. Перебор строки, например через spread, позволяет перейти к кодовым точкам. Но несколько кодовых точек могут образовывать одну графему: это особенно заметно у составных эмодзи и букв с комбинируемыми знаками.

Intl.Segmenter предоставляет сегментацию с выбранной гранулярностью. Для пользовательского счётчика здесь рассматриваем графемы, а не слова. Нельзя просто заменить length на число пробелов или применить словоразделитель и получить нужный лимит.

Проверенные примеры: разные числа без ошибки вычисления

Это собственный учебный расчёт на небольших строках в Node.js с сегментацией для ru. Числа проверены отдельным скриптом; они не являются измерением всех браузеров и серверных библиотек. В строке с буквой е и диакритикой использованы две кодовые точки, а не готовая буква ё.

Учебная строкаЕдиницы UTF-16Кодовые точкиГрафемыБайты UTF-8Что показывает пример
A1111Все четыре меры совпали для этой строки
Я1112Байтовая длина не равна пользовательскому счётчику
😀2114Один эмодзи не обязан занимать одну единицу UTF-16
е + комбинируемое двоеточие сверху2214Даже перебор кодовых точек не равен графемам
👩‍💻53111Составная последовательность объединена в графему
🇷🇺4218Из количества кодовых точек нельзя вывести число флагов на экране

Байтами в таблице названа именно кодировка UTF-8. Это не размер строки в памяти любой программы и не обещание объёма записи в конкретной базе. Для веб-приложения отдельно зафиксируйте технические ограничения выбранного хранилища и внешних систем.

Почему spread не заканчивает исправление

Если проблема заметна на простом улыбающемся эмодзи, переход к кодовым точкам может выглядеть достаточным. Но строка с человеком за компьютером из таблицы всё ещё даст несколько элементов. Проверка только одного удобного примера создаёт ложную уверенность: нужно покрыть разные виды последовательностей, разрешённых полем.

Графема также не является универсальным обещанием одного рисунка во всех шрифтах. Сегментация и отрисовка имеют разные задачи. Если устройство показывает составной знак иначе, это не повод молча менять правило сохранения в зависимости от внешнего вида: пользователь должен получить воспроизводимое объяснение ограничения.

Обрезать по границе, которую вы считаете

Предположим, учебное поле должно сохранить первые две графемы строки A👩‍💻Б. При проверенной сегментации результат — A👩‍💻. Обрезка по первым двум единицам UTF-16 решает другую задачу и может разрезать внутреннее представление знака. Обрезка по двум кодовым точкам тоже не обязана сохранить составную графему целиком.

Выбирайте подстроку по границам сегментов, если обещан именно графемный лимит. Покажите пользователю, что текст сокращён, либо запретите сохранение с объяснением. Для имени, кода или другого значимого поля автоматическое удаление хвоста может быть хуже явного отказа: смысл введённого значения меняется без согласия.

Клиент и сервер должны принимать одно решение

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

При обновлении среды повторяйте набор, особенно если приложение принимает новые последовательности Unicode. Уточняйте поведение неподдерживаемого окружения: согласованный запасной механизм или понятное ограничение возможности. Молчаливый возврат к length способен снова изменить смысл поля для части пользователей.

Графемный лимит не заменяет защиту от огромного ввода

Удобное для человека ограничение не обязано хорошо ограничивать технический объём каждой строки. Поэтому отдельно задайте разумные пределы запроса и хранения, учитывая кодировку и принимающую систему. При их превышении возвращайте честную причину, а не сообщение «слишком много символов», если пользовательский счётчик ещё не достиг предела.

В API-интеграции особенно важно сохранить исходную единицу внешнего ограничения. Если партнёр считает байты или кодовые точки, перед ним нельзя автоматически обещать графемный лимит с тем же числом. Интерфейс может объяснять оба условия, но не скрывать одно другим.

Проверочный набор для формы, сохранения и повторного открытия

  1. Опишите смысл поля и единицу пользовательского лимита. Отдельно перечислите технические пределы приёма и хранения.
  2. Подготовьте обычные буквы, простой эмодзи, составной знак, комбинируемую букву и несколько знаков подряд. Не ограничивайтесь кириллицей и латиницей.
  3. Сверьте результаты счётчика и сервера до лимита, ровно на границе и после неё. Проверьте вставку готового текста наряду с вводом.
  4. Если предусмотрена обрезка, сравните её результат с границами сегментов. Составная графема не должна случайно распадаться.
  5. Сохраните значение, откройте форму снова и проверьте исходный текст и число. Повторное открытие не должно менять согласованный результат.
  6. Перед обновлением runtime повторите тот же набор на клиенте и сервере. Несовпадения разберите до выкладки.

Для мобильного приложения используйте те же контрольные строки, если оно работает с общим API. Это не требует одинакового языка реализации, но требует одинакового договора. Готовность подтверждает совпадение решений о приёме и обрезке, а не одинаковое число на одном снимке экрана.

Вопросы и ответы

Как считать Unicode characters в JavaScript?

Сначала выберите единицу: UTF-16, кодовые точки или графемы. Для пользовательских составных знаков обычный length и перебор кодовых точек не заменяют графемную сегментацию.

Какая granularity нужна в Intl.Segmenter для лимита символов?

Если лимит определён по графемам, выбирайте grapheme. Сегментация на слова решает другую задачу и не даёт эквивалентный счётчик.

Что делать, если Intl.Segmenter is not a constructor?

Проверьте наличие API в целевой среде и выбранный механизм совместимости. Не подменяйте графемную проверку обычным length без явного изменения требований: результаты будут другими.

Нужна помощь с проектом?

Обсудим задачу и предложим решение — от сайта до SaaS и защиты.

Связаться