intl.segmenter 不是 nlp 分词器,仅按 unicode uax-29 规则提供语言合规的字素、词或句边界,适用于光标控制、朗读停顿、多语言高亮与索引构建。

Intl.Segmenter 本身不识别语义,也不能直接做“中文分词”或“歧义消解”,但它能提供符合语言规则的边界信息——这正是高亮与索引增强的关键基础。真正起作用的是:用它获取准确的词元(word)或句子(sentence)边界,再结合原始字符串索引做匹配与标记,从而避开正则粗暴切分导致的日文连写错切、中文单字误拆、中英文混排标点混淆等问题。
明确 Segmenter 的能力边界
它不是 NLP 分词器,不依赖词典或模型,只按 Unicode UAX-29 规则 + locale 数据划分边界。对中文,“word”粒度实际切出的是扩展字素簇(汉字、全角标点、emoji 序列),基本等价于“用户双击选中的单位”;对日文,“ja-JP”下可切出「食べる」「東京都」这类语法单元;对英文,能区分“Dr.”缩写和句号。所以它适合做:
• 光标移动与选区控制
• 朗读停顿点定位
• 基于真实语言边界的高亮与索引
构建词级高亮的核心流程
关键不是“替换成 mark 标签”,而是“在原始字符串中精准定位并包裹”。步骤如下:
- 创建 segmenter 实例:
new Intl.Segmenter('zh-CN', { granularity: 'word' })(注意 locale 必须完整,如 'zh-CN' 而非 'zh') - 对文档文本调用
segmenter.segment(text),遍历得到每个词元的{ segment, index, isWord }——index是该词在原文中的 UTF-16 起始位置 - 对搜索关键词也做同样分词,但匹配逻辑需支持连续词元序列(如搜“微信支付”,要匹配 [“微信”, “支付”] 连续两个词元,而非单个)
- 遍历文档词元数组,用滑动窗口比对关键词词元序列;匹配成功后,根据首个词元的
index和末尾词元的index + segment.length算出闭区间,再用String.slice()截取原始文本并包裹<mark></mark> - 最终插入 DOM 时,只操作文本节点(
nodeType === 3),跳过<script></script>、<style></style>、<textarea></textarea>,并先调用normalize()合并相邻文本节点
断句与多语言混排索引增强
用 granularity: 'sentence' 可靠提取句子边界,尤其适合中日英混排场景:
- 创建
new Intl.Segmenter('zh-CN', { granularity: 'sentence' }),它能正确识别中文「。」、日文「。」、英文「. ! ?」及缩写后的句点 - 对整页文本调用
.segment(),拿到每个句子的index和segment.length,即可构建句子级索引表:{ sentenceId: 0, text: "你好!", start: 0, end: 4 } - 搜索时,先定位到命中句子,再在该句子内用 word 粒度做二级高亮,提升响应速度与上下文相关性
- 富文本处理时,先用
textContent提纯,再分段;高亮结果映射回带标签的 HTML 时,需按字符偏移逐段插入,避免破坏结构
规避常见陷阱
这些细节决定高亮是否稳定可用:
- 不要对 innerHTML 直接正则替换——会截断标签、丢失事件监听器、破坏表单状态
- 不要用词元数组下标当原文位置——中文“你好”和英文“Hello”长度不同,必须用
index字段 - 大小写不敏感匹配,应在分词前统一转小写,但保存原始大小写的
segment和index用于最终渲染 - Safari 14.1+ 才支持
granularity: 'word',旧版需降级为'grapheme'或用 polyfill;Edge 18 及更早不支持,需检测typeof Intl.Segmenter === 'function' - 关键词含正则特殊字符(如
.、?、(),必须先escapeRegExp()再参与匹配逻辑











