一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情。
导言
正则虽好,但不可滥用,要节制。在使用正则表达式的时候,性能不得不考虑,用不好有宕机的风险。在学习如何优化性能之前,我们先了解下正则是如何编译运行的,才好对症下药。
性能优化
有穷状态自动机
这个名词估计你没怎么听过。什么是有穷状态自动机呢?可以这样理解:在一个系统中,存在可枚举数量的状态,每个状态都是有其独特意义的。
有穷状态自动机的具体实现称为正则引擎,主要有两种:DFA和NFA,其中NFA又分为传统的NFA和POSIX NFA。POSIX我们前面提到过POSIX流派,你应该有点印象。下面我们主要来看看DFA和NFA是什么,两者又有什么区别?
匹配过程
const reg = /a(bb)+a/
const str = 'abbbba'
reg.test(str) // true
在上述正则表达式被编译的过程中,可能有如下两种情况:
如果正则匹配完abb之后到了s3状态后,图1中不需要再做任何输入,也能到s1状态,所以说当前状态是在s1还是s3是无感的,这种状态机就称为非确定性有穷状态自动机NFA(Non-deterministic finite automaton),而图2则完全不同,每个状态都需要对应的输入,称为确定性有穷状态自动机DFA(Deterministic finite automaton)。那两者工作方式有什么不同呢?继续往下看:
const reg = /zhangyi(han|nuo|mei)/
const str = 'My name is zhangyinuo.'
reg.test(str)
NFA的工作机制是先看正则,后看文本,以正则为主,就是用正则各分支来不断匹配文本;而DFA的工作机制恰好相反,以文本为主,用文本不断的去和正则去匹配。相比之下,DFA运行效率会更高,因为NFA是以正则为主,文本不断的来匹配正则,而正则是动态的,文本是静态固定的,这样当量词出现的时候会发生回溯。我们现在主流的都是用NFA方式,它虽然可能会有性能问题,但更灵活,功能更强。下面我们就来说说需要注意的性能问题。
优化
提前编译
第一点:在上一篇也说了,构建正则时我们尽量使用字面量方式,其次就是提前编译。
// 建议
const reg = /\w{2,6}\d+/
reg.exec('hyper5')
reg.test('css3')
// 不可取
/\w{2,6}\d+/.exec('hyper5')
/\w{2,6}\d+/.test('css3')
尽量准确表示匹配范围
能确定的部分尽量确定,如校验手机号\d{11}可以,但不够精确,1[3-9]\d{9}更能准确表示匹配范围。
提取公共部分
/abcd|abef/
存在公共的部分要提取出来:
/ab(cd|ef)/
可能性大的放左边
正则是从左往右执行的,例如我们在匹配网址的时候,.com一般比.net常用,那我们尽量把.com放在左边提高匹配效率。
使用子组要节制
正则中括号用于归组,除了少数像环视不作为子组存储外,其他大部分情况默认括号的子组都是保存起来供后续使用的,这样也会降低正则的性能。我们在不需要保存的子组加个?:来表示仅作为归组用而不保存。
警惕嵌套的子组
如果一个组里面包含重复,接着这个组整体也可以重复,比如 (.*)* 这个正则,匹配的次数会呈指数级增长,所以尽量不要写这样的正则。