学习前提
理解g/u标志、test与exec的基本用法;本课检查索引状态和Unicode。
本课完成后你能做什么
正确处理非ASCII文字,并避免g标志造成重复校验跳变。
先理解机制
JavaScript字符串使用UTF-16。普通点号一次通常消耗一个代码单元;u模式按Unicode码点处理代理对,但码点仍不等于用户感知的一个字符,例如字母加组合重音。
\p{L}在u模式下匹配Unicode字母类别,\p{M}匹配组合标记。是否允许数字、空格、标点必须另行定义。不要把\w当作国际化姓名规则。
带g或y的RegExp实例有lastIndex状态。反复test同一输入时会从上次位置继续,失败后归零;普通输入验证通常不需要这两个标志。
可运行示例
console.log(/^.$/.test("😀"), /^.$/u.test("😀"));
const letters = /^\p{L}[\p{L}\p{M}]*$/u;
console.log(JSON.stringify(["学习", "café", "e\u0301", "42"].map(s => letters.test(s))));
const stateful = /a/g;
console.log(stateful.test("a"), stateful.lastIndex);
console.log(stateful.test("a"), stateful.lastIndex);
console.log(/a/.test("a"), /a/.test("a"));
推演与验证
表情在非u单点模式中失败,在u模式中成功。字母规则接受中文、预组合é和组合重音,拒绝42。g测试依次true索引1、false索引0。
常见错误与边界
u不会自动标准化Unicode文本;需要比较规范等价文本时可按需求使用normalize("NFC")。零长度exec循环可能停在同一位置,提取任务优先使用matchAll或明确处理推进。
动手练习
对é和e加组合重音分别使用/^.$/u,解释结果,并在NFC标准化后重试。
参考解法与理由
原输入分别为一个和两个码点,结果true与false。normalize("NFC")后两者都成为预组合é,结果均true;这不意味着所有视觉字符都能变成一个码点。
深入实验:字符长度与正则状态是两条独立轴
一次test成功、下一次同样输入失败,并不一定是字符编码问题;g标志会推进lastIndex。另一方面,一个视觉字符也未必是一个UTF-16代码单元。把这两件事放在小实验里分别观察,避免为状态问题错误地加入u标志。
在现代浏览器Console中独立运行这个实验;它不替换上方主示例。
const globalPattern = /a/g;
console.log(globalPattern.test('a'), globalPattern.lastIndex);
console.log(globalPattern.test('a'), globalPattern.lastIndex);
console.log(globalPattern.test('a'), globalPattern.lastIndex);
const emoji = '😀';
console.log(emoji.length, [...emoji].length);
console.log(/^.$/.test(emoji), /^.$/u.test(emoji));
const decomposed = 'e\u0301';
console.log(decomposed === 'é', decomposed.normalize('NFC') === 'é');
预期观察
输出true 1、false 0、true 1;然后2 1、false true、false true。
原因、取舍与适用边界
普通字段校验一般不需要g;若必须复用有状态对象,明确重置或由专门扫描器管理索引。u让点等构造按Unicode代码点处理,但仍不保证一个匹配对应一个用户感知字符:组合附加符和家庭emoji可能包含多个代码点。名称长度限制应先决定按字节、代码点还是字素簇计数,再选API。正规化也不能作为密码等敏感值的随意预处理。
进阶练习
为何[...text].length仍不能总是用作界面里的“字符数”?
解法与推理
它统计代码点,e加组合重音会计2。若需求是用户感知字符,可在支持的运行环境使用Intl.Segmenter的grapheme粒度,另行定义兼容策略;用组合字符、旗帜和带肤色emoji验证,而不只测英文。