正则表达式的工作原理(一)

223 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

正则表达式的运行效率受许多因素影响,包括匹配的文本、匹配的方式、浏览器引擎...读本文章之前需要在正则表达式使用方面有一定经验,并主要关注如何使他们运行更快。

正则表达式工作原理

编译

使用正则字面量或使用 RegExp 构造函数就可以创建一个正则表达式对象。浏览器会根据你生成的正则对象进行初始化操作:验证你的表达式然后转化为一个原生代码程序,用于执行匹配工作。 如果你把正则对象赋值给一个变量,可以避免重复执行初始化步骤

设置起始位置

在正则类进入使用状态,首先会确认目标字符串的起始搜索位置。它是字符串的起始字符,或者由正则表达式的 lastIndex 属性指定。正则表达式的 lastIndex 属性只作为 exectest 方法的起始搜索位置,并且仅当正则表达式带有/g标识符
浏览器厂商优化正则表达式引擎的方法是通过提前决定跳过一些不必要的步骤来避免大量无意义的工作。如果正则表达式由 ^ 开始,Chrome 通常会判断字符串的起始位置能否匹配:如果匹配失败,那么就停止搜索后续位置。另一个做法是:如果匹配第三个字母是x的字符串,会先找到x,然后再将起始位置回退两个字符。

匹配每个正则表达式字元

一旦正则表达式知道开始位置,它会逐个检查文本和正则表达式模式。当一个特定的字元匹配失败时,正则表达式会尝试回溯到之前尝试匹配的位置上,然后尝试其他可能的路径。

匹配结果

如果在字符串当前位置发现了一个完全匹配,那么正则表达式成功完成匹配。如果正则表达式所有可能的路径都没有匹配到,正则表达式引擎会退回到第二步(设置起始位置),然后从下一个字符重新开始。当字符串的每个字符(包括字符串末尾的空值)都经历这个过程那么正则表达式才会宣告匹配失败。

回溯

回溯是正则表达式强大且富有表现力的根源

大多数正则表达式的实现中,回溯是匹配过程中最为基础且核心的部分。然而回溯会产生大量的计算消耗,而且还容易失控。
当正则表达式尝试匹配目标字符串,它从左到右逐个测试表达式的组成部分,看是否能找到匹配项。遇到量词(*,+?或{2,})和分支( | 操作符)时,需要决策下一步如何处理:何时尝试匹配更多字符;必须从可选项选择一个匹配。

分支与回溯

利用一个正则表达式来模拟正则引擎的工作流程,这个正则表达式匹配 "hello hippo" 或 "happy hippo"。

/h(ello|appy) hippo/.test("hello there, happy hippo");
  1. 匹配过程开始时,首先会查找一个 h,目标字符串的首字母正好是 h,于是立刻锁定起始位置。
  2. 接下来子表达式(ello|appy)提供了两个处理选项。正则表达式选择最左边的选项(分支选择默认从左到右),检查 ello 是否能匹配字符串中下一个字符,匹配成功,正则表达式进而匹配随后的空格,由于 hippo 中的 h 无法匹配到下一个字符的 t,因此这条匹配路径停止。
    因为还未尝试完所有匹配项,随后回溯到最近的决策点(匹配完首字母 h 后面的位置),并尝试匹配第二个分支。
  3. "he" 和 "ha" 匹配没有成功,也没有更多选项,所以正则表达式认为从字符串第一个字符开始匹配是不能成功的,因此从第二个字符开始重新尝试。
  4. 由于首字母是 h,直到搜索到第 14 个字符的位置匹配到 "happy" 中的 h,然后回再次进入分支过程,这次不能匹配到 "hello",但是在回溯尝试第二个分支后,匹配到了完整的字符串 "happy hippo"。
  5. 匹配成功,返回匹配结果。

未完待续...