六、正则表达式的构建

正则的构建需要考虑以下几点的平衡:

  • 匹配预期的字符串
  • 不匹配非预期的字符串
  • 可读性和可维护性
  • 效率

我们还需要考虑这么几个问题:

  • 是否需要使用正则

如能使用其他 API 简单快速解决问题就不需要使用正则:

  1. "2019-03-16".match(/^(\d{4})-(\d{2})-(\d{2})/); // 间接获取 ["2019", "03", "16"]
  2. "2019-03-16".split("-"); // ["2019", "03", "16"]
  3. "?id=leo".search(/\?/); // 0
  4. "?id=leo".indexOf("?"); // 0
  5. "JavaScript".match(/.{4}(.+)/)[1]; // "Script"
  6. "JavaScript".substring(4); // "Script"
  • 是否需要使用复杂正则

/(?!^[0-9]{6,12}$)(?!^[a-z]{6,12}$)(?!^[A-Z]{6,12}$)^[0-9A-Za-z]{6,12}$/

将这个正则拆分成多个小块,如下:

  1. var regex1 = /^[0-9A-Za-z]{6,12}$/;
  2. var regex2 = /^[0-9]{6,12}$/;
  3. var regex3 = /^[A-Z]{6,12}$/;
  4. var regex4 = /^[a-z]{6,12}$/;
  5. function checkPassword (string) {
  6. if (!regex1.test(string)) return false;
  7. if (regex2.test(string)) return false;
  8. if (regex3.test(string)) return false;
  9. if (regex4.test(string)) return false;
  10. return true;
  11. }

1. 准确性

即需要匹配到预期目标,且不匹配非预期的目标。

  • 匹配固定电话

如需要匹配下面固定电话号码,可以分别写出对应正则:

  1. 055188888888 => /^0\d{2,3}[1-9]\d{6,7}$/
  2. 0551-88888888 => /^0\d{2,3}-[1-9]\d{6,7}$/
  3. (0551)88888888 => /^0\d{2,3}-[1-9]\d{6,7}$/

然后合并:

  1. let r = /^0\d{2,3}[1-9]\d{6,7}$|^0\d{2,3}-[1-9]\d{6,7}$|^\(0\d{2,3}\)[1-9]\d{6,7}$/

然后提取公共部分:

  1. let r = /^(0\d{2,3}|0\d{2,3}-|\(0\d{2,3}\))[1-9]\d{6,7}$/

再优化:

  1. let r = /^(0\d{2,3}-?|\(0\d{2,3}\))[1-9]\d{6,7}$/
  • 匹配浮点数

先确定,符号部分([+-])、整数部分(\d+)和小数部分(\.\d+)。

  1. 1.23、+1.23、-1.23 => /^[+-]?\d+\.\d+$/
  2. 10、+10、-10 => /^[+-]?\d+$/
  3. .2、+.2、-.2 => /^[+-]?\.\d+$/

整理后:

  1. let r = /^[+-]?(\d+\.\d+|\d+|\.\d+)$/;
  2. // 考虑不匹配 +.2 或 -.2
  3. let r = /^([+-])?(\d+\.\d+|\d+|\.\d+)$/;
  4. // 考虑不匹配 012 这类 0 开头的整数
  5. let r = /^[+-]?(\d+)?(\.)?\d+$/;

2. 效率

正则表达式运行过程:

  1. 编译
  2. 设定起始位置
  3. 尝试匹配
  4. 若匹配失败则返回前一步重新匹配
  5. 返回匹配成功失败的结果

我们常常优化对 3 和 4 步进行优化:

  • 使用具体字符组替代通配符,消除回溯

/"[^"]*"/ 代替 /".*?"/

  • 使用非捕获型分组

当不需要使用分组引用和反向引用时,此时可以使用非捕获分组。

/^[-]?(?:\d\.\d+|\d+|\.\d+)$/ 代替 /^[-]?(\d\.\d+|\d+|\.\d+)$/

  • 独立出确定字符

加快判断是否匹配失败,进而加快移位的速度。

/aa*/ 代替 /a+/

  • 提取分支公共部分

减少匹配过程中可消除的重复。

/^(?:abc|def)/ 代替 /^abc|^def/

  • 减少分支的数量,缩小它们的范围

/rea?d/ 代替 /red|read/