讲解

元字符是正则语法里有特殊含义的字符,一共十四个:. ^ $ * + ? ( ) [ ] { } | \。它们分别是:. 匹配除换行外的任意单个字符;^ 和 $ 是首尾锚点;* + ? 和 { } 是量词;( ) 用于分组;[ ] 定义字符类;| 表示分支;\ 是转义符。正则的全部表达能力,就建立在这十几个符号的组合之上。

点号 . 是用得最多也最容易踩坑的元字符:它匹配「几乎任意一个字符」——除了换行符(除非加 s 标志)。所以 /a.c/ 能匹配 abc、a-c、a.c,但它要求 a 和 c 之间必须有一个字符,ac 匹配不上。想匹配真正的点号(比如小数点、文件扩展名前面的点),必须转义写成 .。

这些字符「想表示自己」时都要加反斜杠转义:. * + ? ( ) [ ] { } ^ $ | \。有一个重要例外:在字符类 [ ] 内部,大多数字符自动失去特殊含义,[.] 就匹配点号本身,不需要转义。转义一章会系统整理这些规则。

示例

// 点号 . 匹配除换行外的任意单个字符
console.assert(/a.c/.test('abc') === true);
console.assert(/a.c/.test('a-c') === true);
console.assert(/a.c/.test('a.c') === true); // 点号自然也能匹配点号本身
console.assert(/a.c/.test('ac') === false); // 但 a、c 之间必须有恰好一个字符
console.assert(/a.c/.test('a\nc') === false); // 默认不匹配换行

匹配点号本身要转义,这是「3.14」这个经典例子的教训:

// 不转义的点号是通配符:3.14 把 3514、3x14 也一起匹配了
console.assert(/3.14/.test('3514') === true);

// 转义后才是字面的小数点
console.assert(/3\.14/.test('3.14') === true);
console.assert(/3\.14/.test('3514') === false);

元字符全家福,每个例子演示一个符号的特殊含义:

console.assert(/^\d+$/.test('12345')); // ^ $ 锚定整串,\d 数字,+ 一个以上
console.assert(/(ab){2}/.test('abab')); // () 分组,{2} 重复两次
console.assert(/colou?r/.test('color') && /colou?r/.test('colour')); // ? 表示前面的 u 可选
console.assert(/^gray|grey$/.test('grey')); // | 二选一
console.assert(/^[abc]+$/.test('caba')); // [] 字符类:a/b/c 任意组合
console.assert(/a\*b/.test('a*b')); // \ 转义:匹配字面星号

常见坑

  • 忘记转义点号:/example.com/ 会把 example-com、exampleXcom 都匹配上。校验域名、扩展名、小数时,点号必须写成 .。
  • 以为 . 能匹配换行:默认不行。要跨行匹配加 s 标志(标志位一章细讲),或用 [\s\S] 这种「所有字符」的字符类代替。
  • 把 {2} 当字面花括号:/a{2}/ 匹配 aa 而不是 a{2}。想匹配字面花括号要写 { }。
  • 以为元字符处处含义相同:^ 在字符类外是锚点,在 [^...] 里是取反;- 只有在 [ ] 里才是范围符。同一个符号在不同上下文含义不同。

小结

十四个元字符是正则语法的基石;点号是通配符但不跨行,匹配元字符本身要加反斜杠。接下来的几章会逐个深入:字符类、量词、锚点。