六、正则表达式的构建
正则的构建需要考虑以下几点的平衡:
- 匹配预期的字符串
- 不匹配非预期的字符串
- 可读性和可维护性
- 效率
我们还需要考虑这么几个问题:
- 是否需要使用正则
如能使用其他 API 简单快速解决问题就不需要使用正则:
"2019-03-16".match(/^(\d{4})-(\d{2})-(\d{2})/); // 间接获取 ["2019", "03", "16"]
"2019-03-16".split("-"); // ["2019", "03", "16"]
"?id=leo".search(/\?/); // 0
"?id=leo".indexOf("?"); // 0
"JavaScript".match(/.{4}(.+)/)[1]; // "Script"
"JavaScript".substring(4); // "Script"
- 是否需要使用复杂正则
/(?!^[0-9]{6,12}$)(?!^[a-z]{6,12}$)(?!^[A-Z]{6,12}$)^[0-9A-Za-z]{6,12}$/
将这个正则拆分成多个小块,如下:
var regex1 = /^[0-9A-Za-z]{6,12}$/;
var regex2 = /^[0-9]{6,12}$/;
var regex3 = /^[A-Z]{6,12}$/;
var regex4 = /^[a-z]{6,12}$/;
function checkPassword (string) {
if (!regex1.test(string)) return false;
if (regex2.test(string)) return false;
if (regex3.test(string)) return false;
if (regex4.test(string)) return false;
return true;
}
1. 准确性
即需要匹配到预期目标,且不匹配非预期的目标。
- 匹配固定电话
如需要匹配下面固定电话号码,可以分别写出对应正则:
055188888888 => /^0\d{2,3}[1-9]\d{6,7}$/
0551-88888888 => /^0\d{2,3}-[1-9]\d{6,7}$/
(0551)88888888 => /^0\d{2,3}-[1-9]\d{6,7}$/
然后合并:
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}$/
然后提取公共部分:
let r = /^(0\d{2,3}|0\d{2,3}-|\(0\d{2,3}\))[1-9]\d{6,7}$/
再优化:
let r = /^(0\d{2,3}-?|\(0\d{2,3}\))[1-9]\d{6,7}$/
- 匹配浮点数
先确定,符号部分([+-]
)、整数部分(\d+
)和小数部分(\.\d+
)。
1.23、+1.23、-1.23 => /^[+-]?\d+\.\d+$/
10、+10、-10 => /^[+-]?\d+$/
.2、+.2、-.2 => /^[+-]?\.\d+$/
整理后:
let r = /^[+-]?(\d+\.\d+|\d+|\.\d+)$/;
// 考虑不匹配 +.2 或 -.2
let r = /^([+-])?(\d+\.\d+|\d+|\.\d+)$/;
// 考虑不匹配 012 这类 0 开头的整数
let r = /^[+-]?(\d+)?(\.)?\d+$/;
2. 效率
正则表达式运行过程:
- 编译
- 设定起始位置
- 尝试匹配
- 若匹配失败则返回前一步重新匹配
- 返回匹配成功失败的结果
我们常常优化对 3 和 4
步进行优化:
- 使用具体字符组替代通配符,消除回溯
如 /"[^"]*"/
代替 /".*?"/
。
- 使用非捕获型分组
当不需要使用分组引用和反向引用时,此时可以使用非捕获分组。
如 /^[-]?(?:\d\.\d+|\d+|\.\d+)$/
代替 /^[-]?(\d\.\d+|\d+|\.\d+)$/
。
- 独立出确定字符
加快判断是否匹配失败,进而加快移位的速度。
如 /aa*/
代替 /a+/
。
- 提取分支公共部分
减少匹配过程中可消除的重复。
如 /^(?:abc|def)/
代替 /^abc|^def/
。
- 减少分支的数量,缩小它们的范围
如 /rea?d/
代替 /red|read/
。