锚点与单词边界

前面学的字符类、量词都是匹配字符的,这一章讲一类特殊的元素——它们匹配的是位置,不消耗任何字符,称为零宽断言(zero-width assertion)。最常用的就是开头锚点、结尾锚点、单词边界。

开头锚点与结尾锚点

脱字符匹配字符串的开头,美元符号匹配结尾。它们不消耗字符——匹配成功后,引擎的当前位置不前进。这一点很关键,后面会和环视一起深入。

// 锚点(anchor):不消耗字符,只匹配"位置"

^       字符串开头(或多行模式下每行开头)
$       字符串结尾(或多行模式下每行结尾)
\b      单词边界(word boundary)
\B      非单词边界(\b 的反义)
\A      字符串绝对开头(不受多行影响,部分语言支持)
\Z      字符串绝对结尾(部分语言支持)

// 关键:锚点"零宽"——匹配成功后位置不前进
/^hello/.test("hello world");     // true  (开头必须是 hello)
/^hello/.test("say hello");       // false (开头不是 hello)
/world$/.test("hello world");     // true  (结尾必须是 world)
/world$/.test("world peace");     // false (结尾不是 world)

// 同时锚定两端:整个字符串必须完全匹配
/^\d+$/.test("12345");            // true
/^\d+$/.test("12a45");            // false (中间有字母)
/^\d+$/.test(" 12345 ");          // false (两端有空格)

最常见用法是把模式两端都锚定,这样要求"整个字符串完全匹配",而不是"在任意位置搜到"。表单校验几乎都是这种用法——手机号、邮编、密码,都必须整段匹配才算合法。

单词边界

单词边界是"单词字符"(字母、数字、下划线)和"非单词字符"之间的位置。它解决了"想匹配独立单词,但不想被包含在更长单词里"这个高频需求。

// 单词边界(word boundary)

// \b 匹配"单词字符"和"非单词字符"之间的位置
//    单词字符 = [a-zA-Z0-9_]
/cat/.test("catalog");         // true  (cat 在 catalog 里也能匹配)
/\bcat\b/.test("catalog");     // false (cat 前后必须有边界)
/\bcat\b/.test("a cat here");  // true  (cat 是独立单词)
/\bcat\b/.test("my-cat!");     // true  (连字符和感叹号都是边界)

// 常见用途 1:精确匹配某个单词(不被包含在更长单词里)
/\berror\b/g                   // 只匹配独立单词 error,不匹配 errors 或 errorLog

// 常见用途 2:重复单词检测
/\b(\w+)\s+\1\b/              // 匹配 "the the" "is is" 这种重复

// \B 是 \b 的反义:非单词边界
/\Bcat/.test("concat");        // true  (cat 前面是字母,不是边界)
/\Bcat/.test("a cat");         // false (cat 前面是空格,是边界)

经典对比:写 cat 不加边界,会连 catalog、concat 里的 "cat" 也匹配上;加边界后才只匹配独立单词。日常用得最多的两个场景是精确匹配单词检测重复单词(用反向引用配合,见分组一章)。

多行模式

默认情况下,脱字符和美元只匹配整个字符串的开头和结尾。但很多场景下你的输入是多行文本(中间有换行符),希望锚点能匹配每一行的头尾——这时就加 m 标志(multiline)。

// 多行模式(m 标志)如何改变 ^ $ 的含义

// 默认(无 m):^ $ 只匹配整个字符串的开头和结尾
/^foo/.test("bar\nfoo");    // false (^ 只是整个字符串开头)
/foo$/.test("foo\nbar");    // false ($ 只是整个字符串结尾)

// 加 m 标志:^ $ 匹配每行的开头和结尾
/^foo/m.test("bar\nfoo");   // true  (匹配第二行开头)
/foo$/m.test("foo\nbar");   // true  (匹配第一行结尾)

// 实例:统计每行以数字开头的行
"text\n1abc\n2def\nxyz".match(/^\d/mg);
// ["1", "2"]  (注意加了 g 找全部)

// 实例:删掉每行行尾的空白
"line1  \nline2\t".replace(/\s+$/mg, "");
// "line1\nline2"

// 注意:\b 不受 m 标志影响,它本来就是按"字符类型"判断边界的

注意:单词边界不受多行模式影响——它本来就是按"字符类型"判断的,和换行无关。多行模式只改变脱字符和美元的行为。

实战组合

真实项目里,锚点几乎从不单独出现,总是和字符类、量词、分组组合。看几个典型场景:

// 锚点的实战组合

// 1. 校验整段输入是否符合格式(两端锚定)
/^1[3-9]\d{9}$/.test("13812345678");        // 手机号
/^\d{6}$/.test("100000");                   // 邮编
/^[A-Za-z][A-Za-z0-9_]{3,15}$/.test("hello_world");  // 用户名

// 2. 提取每行的某个字段
const lines = "name: Alice\nage: 30\ncity: BJ";
const age = lines.match(/^age:\s*(\d+)/m);
// ["age: 30", "30"]  (用 m 在多行里定位)

// 3. 替换每一行开头的注释符号
"// line1\n// line2".replace(/^\/\//mg, "#");
// "# line1\n# line2"

// 4. 校验同时包含锚点 + 字符类 + 量词
/^[A-Z][a-z]+ \d{4}$/.test("Hello 2026");   // true

小结

下一章讲分组与捕获——圆括号在正则里既是"分组工具"也是"提取数据的钥匙"。

← 上一篇 量词

下一篇 分组与捕获

✈️💬