必须两端同时 normalize,否则匹配一定失败;因 JavaScript 字符串比较按码点逐个比对,而“café”等字符串可能存在 NFC(合成)与 NFD(分解)编码差异,需统一归一化格式才能正确匹配。

必须两端同时 normalize,否则匹配一定失败。 全球化业务里用户输入、API 返回、数据库存储的字符串,哪怕看起来都是 “café”,底层编码可能是 \u00e9(合成)或 e\u0301(分解),JavaScript 的 ===、.includes()、.indexOf() 全部按码点逐个比对,不归一化就永远不相等。
为什么 NFC 是绝大多数场景的默认选择
现代浏览器、iOS/Android 输入法、主流 API(如 Fetch 响应、FormData)返回的文本,天然倾向 NFC 形式。它把能合成的字符尽量合成,比如 "e\u0301" → "é",结果紧凑、可读性好、兼容性强。
实际开发中直接用 .normalize()(不传参)就等价于 .normalize('NFC'),无需额外判断。
- 适合:前端表单校验、搜索关键词高亮、数据库模糊查询前预处理
- 不适合:
NFKC会把全角数字1变成半角1,上标²变成2,可能破坏订单号、SKU 等语义关键字段 - 注意:
NFC对某些罕见多组合符(如带重音+下划线+变音)可能无法完全合成,但这类字符在常规业务中极少出现
匹配时必须两端都 normalize
只对 pattern 调用 .normalize(),而 text 保持原样,结果仍是 false —— 这是最常踩的坑。
- ❌ 错误写法:
text.includes(pattern.normalize()) - ✅ 正确写法:
text.normalize() === pattern.normalize()或text.normalize().includes(pattern.normalize()) - 正则也一样:
new RegExp(pattern.normalize(), 'i').test(text.normalize());不能用字面量/pattern/i,因为无法动态 normalize - 后端查库前也要 normalize:MySQL 的
utf8mb4不自动归一化,入库前或查询前需显式调用CONVERT(… USING utf8mb4)+ 应用层 normalize 逻辑
需要忽略重音时用 NFD + 正则过滤
如果业务要求 “a” 能匹配 “á”“à”“â”,就得先用 NFD 拆解,再移除所有组合标记(\u0300-\u036f)。
示例:
function stripAccents(str) {
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
stripAccents("café") === "cafe"; // true
stripAccents("naïve") === "naive"; // true
-
NFD是唯一可靠拆解预组合字符的方式;String.prototype.codePointAt()或正则/./u都无法分离组合符 - 过滤范围
\u0300-\u036f覆盖了绝大多数拉丁系重音、变音符,但不包括阿拉伯文或天城文的附加符号 - 慎用于中文、日文:这些语言无标准重音系统,
NFD对汉字基本无效,且可能意外影响某些生僻字
真正难处理的不是 normalize 本身,而是数据流中 normalization 的时机——用户输入、API 响应、数据库写入、缓存序列化,只要有一环漏掉,等价性就崩了。最稳妥的做法是:在数据进入系统第一入口(如 Express 中间件、React 表单 submit 处理器)就统一 .normalize('NFC'),后续环节不再假设原始格式。

















