讲解
到目前为止学的元素都匹配「字符」,锚点匹配的是「位置」。^ 匹配字符串的开头,$ 匹配结尾,它们本身不消耗任何字符——/^\d{4}$/ 的意思是:开头、四位数字、结尾,也就是整串必须是四位数字。这就是为什么校验类模式几乎总是 ^ 开头 $ 结尾。
\b 匹配「单词边界」:一个 \w 字符和一个非 \w 字符(或字符串首尾)之间的位置。/\bcat\b/ 能找到独立的单词 cat,而不会撞上 category 里的 cat。它的反面是 \B,匹配「非边界」位置。要特别提醒中文使用者:\b 依赖 \w 的定义,而汉字不属于 \w,所以 \b 对中文文本基本无效——「正则」两个字前后都不是 \w,连整串就是「正则」的文本都匹配不上 /\b正则\b/,别指望它能圈定中文词。
默认情况下 ^$ 只匹配整个字符串的首尾;加上 m(multiline)标志后,它们改为匹配每一行的首尾,这在逐行处理日志、名单时非常有用。最后澄清一个常见混淆:^ 在方括号内([^...])是取反,在方括号外才是锚点,两个含义完全不同。
示例
// ^ 和 $ 钉住字符串首尾
console.assert(/^cat/.test('cat dog')); // 以 cat 开头
console.assert(/^cat/.test('my cat') === false);
console.assert(/cat$/.test('my cat')); // 以 cat 结尾
console.assert(/^cat$/.test('cat')); // 整串就是 cat
console.assert(/^cat$/.test('cat dog') === false);
// 锚点匹配位置不消耗字符:空串也能被 ^$ 描述
console.assert(/^\d*$/.test('')); // 零个或多个数字,空串也算
console.assert(/^$/.test(''));
\b 与 \B:按「词」而不是按「子串」查找。
// \b 单词边界:找独立的 cat,不撞 category
const word = /\bcat\b/;
console.assert(word.test('my cat sleeps'));
console.assert(word.test('category') === false);
console.assert(word.test('copycat') === false); // 前面连着字母,不是边界
// \B 非边界:要求两侧都不是边界
console.assert(/\Bcat\B/.test('concatenate')); // cat 夹在单词中间
console.assert(/\Bcat\B/.test('my cat') === false);
// 提醒:\b 对中文无效,汉字不属于 \w
console.assert(/\b正则\b/.test('正则') === false); // 整串就是「正则」也匹配不上
console.assert(/\bcat\b/.test('cat')); // 英文单词才有边界可言
m 标志让 ^$ 作用于每一行:
const text = 'apple\nbanana\ncherry';
console.assert(/^banana$/.test(text) === false); // 不加 m:^$ 只看整串首尾
console.assert(text.match(/^banana$/m)[0] === 'banana'); // 加 m:匹配某一行
// 逐行提取:每一行的行首都抓一个数字
const list = '1. 苹果\n2. 香蕉\n3. 橙子';
console.assert(list.match(/^\d/gm).join(',') === '1,2,3');
常见坑
- 指望 \b 处理中文:\b 基于 \w 定义,汉字不算 \w,「独立的中文词」无法用 \b 圈定。需要按上下文圈定时改用环视(后面有专门一章)。
- 忘记加 m 标志:处理多行文本时 ^$ 默认只管整串首尾,想逐行锚定必须加 m。
- 混淆 ^ 的两个含义:[^abc] 里是取反,^abc 里是行首锚点。位置决定含义。
- 以为 \b 匹配空格:\b 匹配的是位置,不是任何字符。/\bcat\b/ 匹配到的文本只有 cat 三个字母,不包含两侧空格。
小结
^ $ 锚定首尾、\b 圈定单词边界、m 标志让锚点逐行生效;锚点匹配位置不消耗字符,且 \b 对中文无效。下一节学习把多个元素组织起来的分组与捕获。