- 匹配预期的字符串
- 不匹配非预期的字符串
- 可读性和可维护性
- 效率
正则虽然很强大,但是很多时候并不是万能的。比如匹配没有规律的字符
- 如果可以使用字符串 API,就不需要使用正则
let str = "2017-11-12"
let reg = /^(\d{4})-(\d{2})-(\d{2})/
console.log(str.match(reg))
//[ '2017-11-12','2017','11','12',index: 0,input: '2017-11-12',groups: undefined ]
//使用字符串api
console.log(str.split("-"))
//["2017","11","12"]
- 没有必要构建复杂的正则
- 例如密码匹配问题
reg = /(?!^[0-9]{6,12})(?!^[a-z]{6,12})(?!^[A-Z]{6,12})^[0-9a-zA-Z]{6,12}$/
- 可以使用多个小正则
- 例如密码匹配问题
let reg1 = /^[0-9]{6,12}/
let reg2 = /^[a-z]{6,12}/
let reg3 = /^[A-Z]{6,12}/
let reg4 = /^[0-9a-zA-Z]{6,12}/
function checkPassword (string) {
if (reg1.test(string)) return false
if (reg2.test(string)) return false
if (reg3.test(string)) return false
if (!reg4.test(string)) return false
}
055188888888
0551-88888888
(0551)88888888
- 区号是 0 开头的 3~4 位数:对应的正则是
0\d{2,3}
- 号码是非 0 开头的 7~8 位数字,对应的正则是:
[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}/
保证准确性后,是否需要考虑优化?
- 正则表达式的运行分为如下的阶段
- 编译
- 设定起始的位置
- 尝试匹配
- 匹配失败的化,从下一位开始继续第三步
- 最终结果:匹配成功或失败
let regex = /\d+/g;
console.log( regex.lastIndex, regex.exec("123abc34def") );
console.log( regex.lastIndex, regex.exec("123abc34def") );
console.log( regex.lastIndex, regex.exec("123abc34def") );
console.log( regex.lastIndex, regex.exec("123abc34def") );
// => 0 ["123", index: 0, input: "123abc34def"]
// => 3 ["34", index: 6, input: "123abc34def"]
// => 8 null
// => 0 ["123", index: 0, input: "123abc34def"]
- 当生成一个正则时,引擎会对其进行编译。报错与否出现这这个阶段。
- 当尝试匹配时,需要确定从哪一位置开始匹配。一般情形都是字符串的开头,即第 0 位。
- 但当使用
test
和exec
方法,且正则有g
时,起始位置是从正则对象的lastIndex
属性开始。- 因此第一次
exec
是从第 0 位开始,而第二次是从3
开始的。 - 第一次
exec
,从 0 开始,去尝试匹配,并且成功地匹配到 3 个数字。此时结束时的下标是 2,因此下一次的起始位置是 3。 - 第二次,起始下标是
3
,但第3
个字符是"a"
,并不是数字。但此时并不会直接报匹配失败,而是移动到下一位置,即从第4位开始继续尝试匹配,但该字符是b
,也不是数字。再移动到下一位,是c
仍不行,再移动一位是数字3
,此时匹配到了两位数字34
。此时,下一次匹配的位置是d
的位置,即第8
位。 - 第三次,是从第
8
位开始匹配,直到试到最后一位,也没发现匹配的,因此匹配失败,返回null
。同时设置lastIndex
为 0,即,如要再尝试匹配的话,需从头开始。
- 因此第一次
- 匹配出现问题时上面的第三阶段和第四阶段。优化主要针对于这两个阶段
使用具体的字符组来代替通配符,来消除回溯
- 看这个例子
- 使用
/".*"/
匹配目标字符串"abc"de
正则引擎会保存多种可能中未尝试过的状态,以便后续的回溯时使用,占用一定的内存 - 因此需要具体化字符组,消除布标要的字符
"[^*]*"
- 使用
使用非捕获型分组
- 括号的作用之一是,可以捕获分组和分支里的数据,那么就需要内存来保存它们。
- 当我们不需要使用分组引用和反向引用时,此时可以使用非捕获分组。
- 例如,
/^[-]?(\d\.\d+|\d+|\.\d+)$/
可以修改成/^[-]?(?:\d\.\d+|\d+|\.\d+)$/
。
独立出确定的字符
- 例如
/a+/
可以写成/aa+/
- 由于后者比前者多列确定字符
a
,这样会在第四阶段中,加快判断是否匹配失败
提取分支的公共部分
- 比如,
/^abc|^def/
修改成/^(?:abc|def)/
。 - 比如,
/this|that/
修改成/th(?:is|at)/
。 - 可以减少匹配过程中可消除的重复。
减少分支的数量,缩小它们的范围
/red|read/
可以修改成/rea?d/
.此时分支和量词产生的回溯的成本是不一样的.但这样优化后,可读性会降低的