应使用 String.prototype.normalize() 先标准化再比较:统一调用 .normalize('NFC') 或 .normalize('NFD') 后用 === 判定语义等价,避免因 Unicode 码点差异导致误判。

直接用 === 比较含重音符号、变音标记或多语言组合字符的字符串,常会误判为不等——哪怕人眼看是一样的。根本原因是 Unicode 允许同一语义字符用不同码点序列表示(如预组字符 é vs 基础字符+组合符 e\u0301)。String.prototype.normalize() 就是专为解决这类等价性判定而设计的标准方法。
理解 Unicode 标准化形式:NFC、NFD、NFKC、NFKD
Unicode 定义了四种标准化形式,日常处理多语言文本最常用的是:
-
NFC(Normalization Form C):合成形式。把基础字符和紧跟其后的组合符(如重音)尽量合并成单个预组字符(如
\u00e9表示é)。适合显示、存储和常规比较。 -
NFD(Normalization Form D):分解形式。把预组字符彻底拆成基础字符 + 组合标记(如
é→e\u0301)。适合文本分析、搜索、排序或需要单独处理组合符的场景。 - NFKC/NFKD 含“兼容性”转换(如全角数字转半角、上标¹→1),一般仅在需模糊匹配时使用,可能丢失原始格式信息。
正确做等价性判定:先标准化,再比较
判定两个字符串是否“语义等价”,不能跳过标准化步骤:
- 统一用
.normalize('NFC')或.normalize('NFD')处理双方,再用===比较。 - 两端必须用同一种形式(比如都用
NFC),混用会导致结果不可靠。 - 示例:
'café' === 'cafe\u0301'返回false;但'café'.normalize('NFC') === 'cafe\u0301'.normalize('NFC')返回true。
实际应用中的关键细节
标准化不是万能胶,需注意边界情况:
- 部分语言(如阿拉伯语、梵文)含复杂连字或上下文相关变形,标准化仅解决码点层面等价,不处理渲染或语义歧义。
- 某些组合符顺序在 NFD 下可能不唯一,建议始终用标准库实现(如 JavaScript 的
normalize()),避免手动解析。 - 输入来源不可控时(如用户粘贴、第三方 API),应在入库或比对前主动标准化,而非仅在比对瞬间调用。
- 若需区分视觉差异(如用户明确要区分
é和e\u0301),则不应标准化——先明确业务意图再决定是否归一化。
补充:检查是否已标准化(可选)
JavaScript 不提供直接判断是否已标准化的 API,但可通过“自比较”间接验证:
-
str === str.normalize('NFC')为true,说明该字符串已是 NFC 形式(但不保证所有子串都规范,仅反映当前状态)。 - 更稳妥的做法是:无论输入如何,一律执行
.normalize('NFC')后再使用,消除不确定性。

















