昵称截前 10 位用 substring(0, 10),带 emoji 的名字显示成 小明😀�,最后多一个替换符,接口返回的字符串长度也对不上。第一次遇到会往复杂处想,其实漏掉的多是个小前提。

Array.from('小明😀测试').slice(0, 4).join('')
new Intl.Segmenter('zh', {granularity: 'grapheme'}).segment('小明😀')
console.log('😀'.length)

JS 字符串按 UTF-16 码元计数,一个 emoji 占两个码元,按下标切正好落在中间。替换符是孤立代理项渲染出来的结果。这一点先确认清楚,后面就不用反复试。

碰到「substring 截断 emoji」这类问题,我习惯先把当前配置原样备份一份,改坏了立刻换回来,不用现场推。把复现步骤压到三条以内,交给同事也能跑出来,才算真的定位清楚。

改动写完就同步给同事,下一个人踩同一个坑,成本还是你的。验证用真实数据走一遍,测试数据太干净,边界情况根本碰不到。

能分两步做的操作就别合成一步,中间看一眼返回值,出问题好判断。

具体做法是,改用 Array.from(str) 按码点切分再 join;需要按字素簇切时用 Intl.Segmenter;后端长度校验也按码点或字素 不要用 UTF-16 length;切完检查有没有残留的 U+FFFD 替换符。

改完照着核一遍,截断结果末尾没有替换符 显示宽度和预期字数一致。

作者 9823189