这是我参与8月更文挑战的第12天,活动详情查看: 8月更文挑战
前言
正则表达式能够帮助前端开发者更高效地匹配到目标字符串,从而完成相应具体的字符串操作,但并非任意编写满足功能的正则表达式都能达到我们对性能的期望。
在讨论如何编写更高效的正则表达式之前,我们有必要先来了解一下其工作原理。
正则表达式处理步骤
- 编译表达式:一个正则表达式对象可以通过RegExp构造函数创建,也可以定义成正则直接量,当其被创建出来后,浏览器会先去验证然后将它转化为一个原生待程序,用于执行匹配任务。
- 设置起始位置:当开始匹配时,需要先确定目标字符串的起始搜索位置,初始查询时为整个字符串的起始字符的位置,在回溯查询时为正则表达式对象的lastIndex属性指定的索引位置。
- 匹配过程;在确定了起始位置后,便会从左到右逐个测试正则表达式的各组成部分,看能否找到匹配的字元。如果遇到表达式中的量词和分支,则需要进行决策,对于量词(*、+或{3}),需要判断从何时开始进行更多字符的匹配尝试;对于分支(“|”或操作),每次需从选项中选择一个分支进行接下来的匹配。在正则表达式做这种决策的时候,会进行记录以备回溯时使用。
- 匹配结束:若当前完成一个字元的匹配,则正则表达式会继续向右扫描,如果接下来的部分也都能匹配上,则宣布匹配成功并结束匹配;若当前字元匹配不到,或者后续字元匹配失败,则会回溯到上一个决策点选择余下可选字元继续匹配过程,直到匹配到目标子字符串,宣布匹配成功,或者尝试了所有排列组合后也没找到,则宣布匹配失败,结束本轮匹配。回到起始字符串的下一个字符,重复匹配过程。
分支与重复
接下来通过一个具体例子,看看正则表达式在匹配过程中是如何处理分支与重复的:
/<img|p)>.*</img>/.test("<p><img src='photo.jpg' /></p>")
开始匹配时,首先从左向右查找<字符,恰好目标字符串的第一个字符是<,然后进行img|p分支子表达式的处理,分支选择也是从左到右的,先检查image是否能匹配成功,发现字符<随后的字符p并不能匹配,此分支无法继续。需要回溯到最近一次的分支决策点,即首字符<的后面,尝试第二个分支p字符的匹配,发现匹配成功。
接下来的.号表示匹配除换行符外的任意字符,并且带有量词符号*,这是一个贪婪量词,需要重复0次或多次的匹配.号,由于目标字符串中不存在换行符,所以它会过滤掉接下来的所有字符,至字符串尾部往回继续匹配接下来的字元,最后一个字符为>,不匹配所需的<,尝试倒数第二个字符p,p也不匹配,如此循环直到匹配到目标字元或找不到目标字元匹配失败。
如果我们仅想查找距离字元<(img|p)最近的,显然这种贪婪量词的搜索过程会扩大正则表达式的匹配空间,为此可以使用惰性量词.*?进行替换。
可以看出这里的目标字符串,对于贪婪量词与惰性量词的正则匹配结果是相同的,但它们的匹配过程却是不同的,所以当目标字符串变得非常大时,获得相同匹配结果的正则表达式,其执行性能可能会存在较大差异。