外观
构建
一、平衡法则
- 匹配预期的字符串
- 不匹配非预期的字符串
- 可读性和可维护性
- 效率
二、构建正则前提
是否能使用正则
是否有必要使用正则
- 日期提取年月日
js
const regex = /(\d{4})-(\d{2})-(\d{2})/
const str = "2023-09-18"
str.match(regex)
console.log(RegExp.$1) // "2023"
console.log(RegExp.$2) // "09"
console.log(RegExp.$3) // "18"
采用 split 替换
js
const str = "2023-09-18"
console.log(str.split("-"))
是否有必要构造一个复杂的正则
- 密码匹配问题
要求密码长度 6-12 位,由数字、小写字符和大写字母组成,但必须至少包括 2 种字符
/((?=.*[0-9])(?=.*[a-z])|(?=.*[0-9])(?=.*[A-Z])|(?=.*[a-z])(?=.*[AZ]))^[0-9A-Za-z]{6,12}$/;
改用多个小正则替换
js
const regex1 = /^[0-9A-Za-z]{6,12}$/;
const regex2 = /^[0-9]{6,12}$/;
const regex3 = /^[A-Z]{6,12}$/;
const 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;
}
三、准确性
所谓准确性,就是能匹配预期的目标,并且不匹配非预期的目标
主要的解决思路是: 针对每种情形,分别写出正则,然用分支把它们合并在一起,再提取分支公共部分,就能得到准确的正则
匹配固定号码
js
055188888888
0551-88888888
(0551)88888888
- 匹配 "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}$/
- 明确形成关系, 三者形成或的关系, 可以构建分支
/^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}$/
- 化简得到
/^(0\d{2,3}-?|\(0\d{2,3}\))[1-9]\d{6,7}$/
上面的正则构建过程略显罗嗦,但是这样做,能保证正则是准确的。
匹配浮点数
js
1.23、+1.23、-1.23
10、+10、-10
.2、+.2、-.2
- 符号部分
[+-]
, 整数部分\d+
, 小数部分\.\d+
, 写出如下正则/^[+-]?(\d+)?(\.\d+)?$/
, 但是会匹配空字符""
, 因为目标字符串不是要求每部分都是可选的 - 要匹配
1.23、+1.23、-1.23
:/^[+-]?\d+\.\d+$/
- 要匹配
10、+10、-10
:/^[+-]?\d+$/
- 要匹配
.2、+.2、-.2
:/^[+-]?\.\d+$/
- 提取公共部分得到
/^[+-]?(\d+\.\d+|\d+|\.\d+)$/
四、效率
保证了准确性后,才需要是否要考虑要优化。大多数情形是不需要优化的,除非运行的非常慢。
正则表达式的运行分为如下的阶段:
- 编译
- 设定起始位置
- 尝试匹配
- 匹配失败的话, 从下一位开始继续第 3 步
- 最终结果: 匹配成功或失败
效率的瓶颈主要出现在上面的第 3 阶段和第 4 阶段
使用具体型字符组来代替通配符, 来消除回溯
匹配 123"abc"456
时 采用 /"[^"]*"/
代替 /".*"/
使用非捕获型分组
括号的作用之一是,可以捕获分组和分支里的数据。那么就需要内存来保存它们
当我们不需要使用分组引用和反向引用时,此时可以使用非捕获分组。
例如,/^[-]?(\d\.\d+|\d+|\.\d+)$/
可以修改成:/^[-]?(?:\d\.\d+|\d+|\.\d+)$/
独立出确定字符
例如,/a+/
可以修改成 /aa*/
, 确定了字符"a", 在第四步中, 加快判断是否匹配失败, 进而加快移位速度。
提取分支公共部分
比如,/^abc|^def/
修改成 /^(?:abc|def)/
。
减少分支数量, 缩小它们的范围
/red|read/
可以修改成 /rea?d/
此时分支和量词产生的回溯的成本是不一样的。但这样优化后,可读性会降低的。