正则表达式:模式匹配入门 · 第 4 / 6 节

第 4 课:钉住匹配位置的锚点

本课目标:

  • 把匹配锚定在字符串的开头(^)或结尾($)。
  • 用词边界 \b 匹配完整单词。
  • 解释锚点为什么是零宽的:它们匹配位置,不匹配字符。

先修条件:第 1-3 课(字面量、字符类、量词) | 上一篇 << 03 | 下一篇 05 >>

你的模式匹配 “12345”,也匹配 “a12345z” 里的那个 “12345”

从第 1 课你就知道,模式只要在字符串任何位置被找到就算成功。这通常正是你想要的,直到它不是。/\d{5}/ 匹配 “12345”,但它也匹配埋在 “a12345z” 里的 “12345”,所以拿它来校验“一个字符串由五位数字组成”很不称职。要说出“并且周围没有别的东西”,你需要另一种元字符:锚点。锚点根本不匹配字符,它们匹配的是位置。这个想法一旦点透,就是“在某处找到它”和“整个字符串恰好是它”的分界线。

讲解

两个锚点标记输入的两端1

  • ^ 匹配字符串的开头/^cat/ 只在 “cat” 位于最开头时匹配。
  • $ 匹配字符串的结尾/cat$/ 只在 “cat” 位于最结尾时匹配。

把它们一起用,就能要求整个字符串恰好是这个模式、再无其他。/^\d{5}$/ 读作:开头,然后恰好五位数字,然后结尾,所以前后都没有余地容纳任何别的东西。这正是 /\d{5}/ 永远成不了的那个校验器。

第三个锚点是词边界(word boundary)\b。它匹配单词字符(字母、数字、下划线)与非单词字符之间的位置,比如字母与空格的交界处,或者字符串的开头和结尾1。它的全部用途就是匹配完整单词/\bcat\b/ 匹配作为独立单词的 “cat”,但不匹配 “category” 里的 “cat”。

这三个锚点最关键的一点是它们都是零宽(zero-width)的:词边界匹配的是一个位置,匹配长度为零,不消耗任何字符1\b^$ 一样是锚点;它在一个位置上匹配,而且这个匹配是零长度的2。所以 \b 不会给匹配添加任何字符,它只是断言“这里有一条边界”。忘了这一点,你就会期待 /\bcat\b/ 抓住 “category” 里的 “cat”:它不会,因为 “t” 和 “e” 之间没有边界。

(一条留给以后阅读的注记:加上多行 m 标志后,^$ 还会在每一行的开头和结尾匹配,而不只是整个字符串1。这里用不到它,但它能解释你以后在别处见到的模式。)

完整示例(跟着做)

你想校验一个字符串是一个完整的数、再无其他:前面没有标签,后面没有单位。两端都上锚:

一步步看:^ 迫使匹配从开头开始,\d+ 消耗一个或多个数字,$ 迫使结尾紧随其后。“42” 严丝合缝,什么都不剩。“42px” 失败,因为数字之后 $ 要求的是结尾,可 “px” 还在那里。“ 42” 失败,因为 ^ 要求最开头就是数字,它却撞见一个空格。锚点没有添加任何字符:它们只是把数字从两侧圈了起来。

你来试试(填空示例)

你想匹配完整单词 “log”:在 “please log in” 里命中,在 “blog” 和 “logistics” 里都不命中。填上两个锚点:

答案:两个空都填 \b,得到 /\blog\b/。前面的 \b 要求 “l” 之前有一条边界(所以 “blog” 失败:“b” 和 “l” 都是单词字符,没有边界),后面的 \b 要求 “g” 之后有一条边界(所以 “logistics” 失败)。在 “please log in” 里,“log” 两侧的空格在两边都制造了边界,所以匹配成功。两个 \b 都没有消耗字符,各自只是断言那里存在一条边缘。

小结 + 下节课预告

锚点匹配位置而不是字符:^ 是开头,$ 是结尾,\b 是单词字符与非单词字符之间的词边界。它们是零宽的,不消耗任何东西就把匹配圈起来;而 ^…$ 合起来就是“整个字符串恰好是它”的说法。你现在能写真正的校验器了。下一课我们要用捕获组从匹配里取出片段,并用 | 运算符提供备选。

Footnotes

  1. MDN: Assertions (anchors and boundaries) — https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Assertions 2 3 4

  2. regular-expressions.info: Word Boundaries — https://www.regular-expressions.info/wordboundaries.html

练习

01

在你的控制台里,定义 const s = "cat scatter cats";。先预测再运行:s.match(/cat/)、s.match(/^cat/) 和 s.match(/\bcat\b/)。观察每一个都落在了哪里。

第 1 级(热身)
完成标准 · 本地勾选
02

写一个模式,校验一个字符串恰好是 “#a1b2c3” 这样的小写十六进制颜色:一个 #,然后恰好六个字符,每个都是 0-9 或 a-f,再无其他。用 "#a1b2c3"(通过)和 "#a1b2c3 "(失败,末尾有空格)测试它。然后解释去掉 $ 会坏在哪里。

第 2 级(进阶)
完成标准 · 本地勾选

我的笔记

记下想法、痛点、没懂的地方。只写进这门课的附录,正课文件不动。