跳到正文

别再用 length 统计用户文本:一次 Unicode 字符数和 ESLint 护栏复盘

这次问题最初很小:昵称编辑框右下角有一个字数计数,代码直接用了 value.length。普通中文、英文都没问题,遇到 emoji 和国旗时就不对了。

😀 会被算成 2,🇯🇵 会被算成 4。用户看到的是一个字符,前端计数却像在数底层编码单元。这个 bug 一旦出现在昵称、签名、备注、举报描述、搜索词、弹窗 prompt 这类用户可见输入里,表现会很别扭:计数提前超限、截断把 emoji 切坏、头像首字母取不到用户真正看到的第一个字符,或者原生 maxlength 在浏览器输入阶段就提前阻止继续输入。

问题从输入框计数开始,但修复同时涉及文本单位、截断边界、组件内部限长和新增代码的检查。三篇分别解释机制、实际输入处理与静态护栏;接口如果限制码点或字节,继续遵循真实协议。

阅读顺序

篇目 要回答的问题
1.用户眼里的一个字,与 UTF-16 的一个单元 一个可见符号怎样由多个码点与编码单元组成,原生字符串 API 在数什么。
2.计数、截断与输入框必须共用边界 字素分段、接口码点预算和组件内部截断怎样保持一致。
3.用 ESLint 发现用户文本的危险操作 怎样根据类型与名字线索发现用户文本的危险操作,并处理误报和迁移。

阅读范围

第 2 篇包含工具函数工作区与输入组件的接入边界,第 3 篇提供 ESLint 实现与迁移样例。字数与接口预算分别按产品和协议定义,fallback 覆盖、输入法行为与静态误报在对应位置说明。

章节定位

按章节名可以直接进入对应正文或补充材料。

1.用户眼里的一个字,与 UTF-16 的一个单元:产品里的「一个字」按什么算 · Unicode 组合边界:一个字可能由多个部分组成 · 补充平面字符:一个编号,两个 UTF-16 单元 · 旗帜 emoji:两个区域符号拼成一面旗 · 肤色修饰:modifier 要跟着前一个 emoji 一起看 · ZWJ 组合:看起来一个整体,底下是一串 emoji · Combining mark:重音符号依附在基础字符上 · Variation selector:同一个字符也能选择显示样式 · Keycap sequence:按键帽是拼出来的 · Indic conjunct:复杂书写系统也有组合边界 · Spacing mark:看得见宽度,也可能仍是一个整体 · Unicode 这层到底发生了什么 · 字怎么表示:从符号到编码单元 · Unicode、UTF-8 和 UTF-16 分别解决什么 · UCS-2 和 UTF-16 差在哪里 · JavaScript 为什么会数到 UTF-16 code unit。

2.计数、截断与输入框必须共用边界:length 为什么有问题 · 用 Intl.Segmenter 把用户感知字符切出来 · Intl 是 JavaScript 的国际化能力入口 · Segmenter 找的是边界,不会改变字符串本身 · 项目里只暴露业务意图 · fallback 只承诺兜住高风险边界 · 原生 maxlength 也要让位给受控输入 · 接口按码点限制时,计数和截断要分别选择 · 组件库的差别在于谁负责截断。

3.用 ESLint 发现用户文本的危险操作:先抓真正危险的字符串操作 · 语义判断比粗暴禁用更重要 · 常量名单和规则实现分开维护 · 黑白名单支持项目追加 · Warning 起步更适合团队迁移 · 总结 · 验证同时覆盖工具、规则和业务组件。