贪婪模式

使用replace编写一个函数移除 JavaScript 代码中的所有注释也是可能的。这里我们尝试一下:

  1. function stripComments(code) {
  2. return code.replace(/\/\/.*|\/\*[^]*\*\//g, "");
  3. }
  4. console.log(stripComments("1 + /* 2 */3"));
  5. // → 1 + 3
  6. console.log(stripComments("x = 10;// ten!"));
  7. // → x = 10;
  8. console.log(stripComments("1 /* a */+/* b */ 1"));
  9. // → 1 1

或运算符之前的部分匹配两个斜杠字符,后面跟着任意数量的非换行字符。多行注释部分较为复杂,我们使用[^](任何非空字符集合)来匹配任意字符。我们这里无法使用句号,因为块注释可以跨行,句号无法匹配换行符。

但最后一行的输出显然有错。

为何?

在回溯一节中已经提到过,表达式中的[^]*部分会首先匹配所有它能匹配的部分。如果其行为引起模式的下一部分匹配失败,匹配器才会回溯一个字符,并再次尝试。在本例中,匹配器首先匹配整个剩余字符串,然后向前移动。匹配器回溯四个字符后,会找到*/,并完成匹配。这并非我们想要的结果。我们的意图是匹配单个注释,而非到达代码末尾并找到最后一个块注释的结束部分。

因为这种行为,所以我们说模式重复运算符(+*?{})是“贪婪”的,指的是这些运算符会尽量多地匹配它们可以匹配的字符,然后回溯。若读者在这些符号后加上一个问号(+?*???{}?),它们会变成非贪婪的,此时这些符号会尽量少地匹配字符,只有当剩下的模式无法匹配时才会多进行匹配。

而这便是我们想要的情况。通过让星号尽量少地匹配字符,我们可以匹配第一个*/,进而匹配一个块注释,而不会匹配过多内容。

  1. function stripComments(code) {
  2. return code.replace(/\/\/.*|\/\*[^]*?\*\//g, "");
  3. }
  4. console.log(stripComments("1 /* a */+/* b */ 1"));
  5. // → 1 + 1

对于使用了正则表达式的程序而言,其中出现的大量缺陷都可归咎于一个问题:在非贪婪模式效果更好时,无意间错用了贪婪运算符。若使用了模式重复运算符,请首先考虑一下是否可以使用非贪婪符号替代贪婪运算符。