第 4 课:钉住匹配位置的锚点
本课目标:
- 把匹配锚定在字符串的开头(
^)或结尾($)。- 用词边界
\b匹配完整单词。- 解释锚点为什么是零宽的:它们匹配位置,不匹配字符。
你的模式匹配 “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
-
MDN: Assertions (anchors and boundaries) — https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Assertions ↩ ↩2 ↩3 ↩4
-
regular-expressions.info: Word Boundaries — https://www.regular-expressions.info/wordboundaries.html ↩
练习
写一个模式,校验一个字符串恰好是 “#a1b2c3” 这样的小写十六进制颜色:一个 #,然后恰好六个字符,每个都是 0-9 或 a-f,再无其他。用 "#a1b2c3"(通过)和 "#a1b2c3 "(失败,末尾有空格)测试它。然后解释去掉 $ 会坏在哪里。
第 2 级(进阶)我的笔记
记下想法、痛点、没懂的地方。只写进这门课的附录,正课文件不动。