一、前言

粗浅的编写正则表达式,是造成性能瓶颈的主要原因。如下:

var reg1 = /(A+A+)+B/;

var reg2 = /AA+B/;

上述两个正则表达式,匹配效果是一样的,但是,效率就相差太远了,甚至在与少量字符串匹配时,reg1就会造成你浏览器卡死。

不信?我们可以测试下。

首先,我们声明一个字符串变量str,同时赋予一个包含20个A的字符串给str,采用match方法与上述reg1、reg2进行匹配测试,如下:

var str = 'AAAAAAAAAAAAAAAAAAAA';

str.match(reg1);

str.match(reg2);

在浏览器中运行该段代码,发现一切正常嘛。

然而,随着,我们不断向变量str中添加A后,重复测试,在某一刻(取决于你的浏览器),reg1就会让我们的浏览器挂起,但,回头看看最终的str字符串长度,却还不到50。而,reg2却安然无恙。

心里有一丝疑问,是什么造成了它们如此巨大的差别?以后我们在写正则表达式时,又该如何避免防范这类问题呢?

那么,接下来,我们就有必要深入理解JavaScript正则表达式的内部执行原理了。

如果,在此你还不是很了解正则表达式,那么可以参考如下两篇博客后,再前来,小生在此等候。

理清JavaScript正则表达式--上篇

理解JavaScript正则表达式--下篇

二、正则表达式工作原理

为了高效的使用正则表达式,理解它们的工作原理是很重要的。

具体如下:

-Step1.编译

当我们创建一个正则表达式(字面量或者RegExp对象)后,浏览器会检查该正则的模板是否符合标准,然后将其转化成内部代码,用于执行匹配工作。

所以,如果我们将正则表达式赋予一个变量,可以避免重复执行该‘编译’步骤。

-Step2.设置开始位置

当我们使用Step1中编译后的正则表达式时,首先它将确定从目标字符串中什么位置进行匹配。通常,是目标字符串的起始位置,或者由正则表达式的lastIndex属性指定。

但是,当它从Step4(匹配失败)中返回时,该位置则为匹配失败的位置的下一个位置。

-Step3.正则匹配

当经历Step2后,正则表达式将从指定位置,从左到右,与目标字符串,逐个匹配。若,正则表达式在匹配过程中,遇到某个字元匹配不了时,它不会立即失败,而是尝试回溯到最近一个决策点,然后在剩余选项中选择一个,以求继续能匹配。

-Step4.匹配结果

当经历Step3后,发现能与正则匹配成功的子字符串,那么就匹配成功。如果,经历了Step3后,发现没有能与正则匹配的子字符串,那么,它将回到Step2,继续。只有当目标字符串中的每个字符(以及最后一个字符后面的位置)都经历了Step3后,仍没有找到匹配项,才宣布失败。

下面就举个例子,使我们更透彻地明白以上4步。

如下:

var reg = /A(B|C)D/g;

var str = 'ABCACD';

reg.exec(str);

①首先,浏览器将解析reg正则表达式(Step1)。

②然后,由于是首次匹配,所以确认开始位置即为字符串起始位置(Step2)。

③首先由正则的第一个字元A与字符串起始位置字符A匹配,成功,并在之后的位置记录一个决策点,因为后面有分支嘛;然后由 (B|C)分支中的B选项去匹配字符串的B,发现匹配;然后再由正则下一个字元D去匹配目标字符串第三个字符C,发现不匹配,但是并没有放弃,而是回溯,查看是否有决策点,发现有,回溯到就近一个决策点(字符串首字母A之后的那个位置上),尝试利用第二个分支选项C去匹配字符串第二个字符B,发现不匹配,回溯,查询是否还有其他分支选项,发现没有,然后宣布该次失败(Step3)。

④经历Step3后,发现没有与正则匹配的子字符串,但是,与之匹配的目标字符串的匹配位置并不是最后一个位置,所以,回到Step2,从目标字符串的下一个位置(即,字符串首字母A之后的那个位置上)开始匹配。首先由正则表达式的第一个字元A与目标字符串B匹配,不成功,又无回溯点,故而,进入Step4,判断是否是最后一个位置,发现不是,又跳到Step2中继续。

⑤就这样一步一步,来到了目标字符串的第四个位置,首先A去与目标字符串的第三个字符A匹配,成功;接下来就是由分支(B|C),去匹配C,首先由分支中的第一个选项B去与C匹配,发现没有成功,回溯到就近一个决策点,尝试利用第二个分支选项C匹配,成功,紧接着D也成功了。

⑥匹配成功,并将lastIndex置为6。

三、回溯

上述“正则表达式工作原理”一小节,Step3中的回溯我们是一笔带过的。但是,可不要忽略了,回溯在正则中是非常重要的,如果理解得不明白,我们在编写正则时,很容易造成回溯失控。

下面我们就来一起看看回溯在正则表达式中的运用。

正则表达式中有两种情况,会制造回溯点:

-分支(通过|操作符)

-量词(诸如*,+?,或者{…})

下面我们就分别举例来看看。

--分支和回溯--

对于分支,详见‘正则表达式工作原理’小节中Demo。

--量词和回溯--

在量词中,有贪婪量词(诸如*,+)和非贪婪量词(诸如*?,+?)之分。所以回溯形式对于它们而言也就有差别咯。我们首先写个demo看看贪婪量词是怎么回溯的。

Demo如下:

var reg = /\w*D/g;

var str = 'MonkyDorie!';

reg.exec(str);

就上述贪婪模式匹配流程如下:

提醒:正则表达式reg中\w表示匹配“字母、数字或下划线”,*这个贪婪量词表示重复匹配零次或者多次,由于是贪婪量词,故而它会尽可能多的匹配。

①首先,正则中的\w*与目标字符串匹配,会一直匹配到‘!’之前,即’MonkyDorie’,并且,每个匹配位置都会记录一个决策点,便于回溯。

②然后,由正则中的剩余字元D与字符串中!匹配,匹配失败;但是,它并没有放弃(因为在此之前,记录了决策点),而是回溯到就近一个决策点(字符e的前一个位置),然后正则D与字符e匹配,匹配失败;再回溯到就近一个决策点(字符i的前一个位置),然后正则D与字符i匹配,匹配失败;就这样一直回溯到字符D的前一个位置时,正则D与字符D匹配,匹配成功,并置lastIndex为6。

好了,这就是上述贪婪匹配流程。

随后,我们将上述Demo中的正则表达式,稍微调整下,在*后面加上?,变成非贪婪模式,看看非贪婪量词是怎么回溯的。

Demo如下:

var reg = /\w*?D/g;

var str = 'MonkyDorie!';

reg.exec(str);

就上述非贪婪模式匹配流程如下:

提醒:正则表达式reg中\w表示匹配“字母、数字或下划线”,*?是个非贪婪量词,也表示重复匹配零次或者多次,但是由于是非贪婪量词,故而它会尽可能少的匹配。

首先,正则中的\w*?会选择匹配零个字符(尽可能少的匹配),并将第一个位置(字符M的前一个位置)记录一个决策点,继而轮到字元D与字符串字符M匹配,匹配失败;回溯到就近一个决策点(字符M的前一个位置),然后\w*?选择匹配一个字符M,并记录一个回溯点(第二个字符o的前一个位置),继而轮到字元D与字符串字符o匹配,匹配失败;回溯到就近一个决策点(字符o的前一个位置),就这样一步一步,当\w*?选择匹配五个字符Monky时,继而轮到字元D与字符串字符D匹配,匹配成功,并置lastIndex为6.

上述两Demo,对比图如下:

四、利用前瞻和后向引用避免回溯

正如上述‘回溯’小节中谈到,重复量词和分支会记录决策点,引起回溯。但是,如果在实际需求中,我们不想让它们记录决策点呢—因为回溯太多就会导致回溯失控,影响性能,正如我们在‘前言’中看到的那样。

一些正则表达式引擎,支持一种叫做原子组的属性。原子组,写作(?>…),省略号表示任意正则表达式模板。存在原子组中的正则表达式组中的任何决策点都将被丢弃。利用原子组,我们就可以在必要时,消除由重复量词和分支记录的决策点了。

但,在JavaScript中不支持原子组,怎么办呢?

我们可以利用前瞻来模拟原子组,但是,前瞻在整个匹配过程中,是不消耗字符的,它只是检查自己包含的模板是否能在当前位置匹配。然而,我们又可以利用后向引用解决此问题,如下:

(?=(pattern to make atomic))\1

好了,针对‘利用前瞻和后向引用避免回溯’一节,我们写个Demo,自我测试下:

var str = 'ABCDM';   //目标字符串

var reg1 = /\w*M/;   //贪婪模式

var reg2 = /(?=(\w*))\1M/;  //贪婪模式,使用前瞻和后向引用

var reg3 = /\w*?M/;    //非贪婪模式

var reg4 = /(?=(\w*?))M/;    //非贪婪模式,使用前瞻和后向引用

对于以下匹配结果,各位看官答对否:

str.match(reg1);

str.match(reg2);

str.match(reg3);

str.match(reg4);
五、参考文献

[2]正则基础之——环视

JavaScript正则表达式,你真的知道?的更多相关文章

  1. JavaScript正则表达式详解(一)正则表达式入门

    JavaScript正则表达式是很多JavaScript开发人员比较头疼的事情,也很多人不愿意学习,只是必要的时候上网查一下就可以啦~本文中详细的把JavaScript正则表达式的用法进行了列表,希望 ...

  2. JavaScript正则表达式的模式匹配教程,并且附带充足的实战代码

    JavaScript正则表达式的模式匹配 引言 正文 一.正则表达式定义 二.正则表达式的使用 三.RegExp直接量 (1)正则表达式初体验 (2)深入了解正则 字符类 重复 选择 分组与引用 指定 ...

  3. 【JS】javascript 正则表达式 大全 总结

    javascript 正则表达式 大全 总结 参考整理了一些javascript正则表达式 目的一:自我复习归纳总结 目的二:共享方便大家搜索 微信:wixf150 验证数字:^[0-9]*$ 验证n ...

  4. 理清JavaScript正则表达式--上篇

    在JavaScript中,正则表达式由RegExp对象表示.RegExp对象呢,又可以通过直接量和构造函数RegExp两种方式创建,分别如下: //直接量 var re = /pattern/[g | ...

  5. 理清JavaScript正则表达式--下篇

    紧接:"理清JavaScript正则表达式--上篇". 正则在String类中的应用 类String支持四种利用正则表达式的方法.分别是search.replace.match和s ...

  6. JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解

    二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 ...

  7. Python自动化 【第十八篇】:JavaScript 正则表达式及Django初识

    本节内容 JavaScript 正则表达式 Django初识 正则表达式 1.定义正则表达式 /.../  用于定义正则表达式 /.../g 表示全局匹配 /.../i 表示不区分大小写 /.../m ...

  8. JavaScript正则表达式下——相关方法

    上篇博客JavaScript 正则表达式上——基本语法介绍了JavaScript正则表达式的语法,有了这些基本知识,可以看看正则表达式在JavaScript的应用了,在一切开始之前,看看RegExp实 ...

  9. JavaScript 正则表达式上——基本语法

    定义 JavaScript种正则表达式有两种定义方式,定义一个匹配类似 <%XXX%> 的字符串 1. 构造函数 var reg=new RegExp('<%[^%>]+%&g ...

随机推荐

  1. Npm包的开发

    个人开发包的目录结构 ├── coverage //istanbul测试覆盖率生成的文件 ├── index.js //入口文件 ├── introduce.md //说明文件 ├── lib │   ...

  2. solr服务中集成IKAnalyzer中文分词器、集成dataimportHandler插件

    昨天已经在Tomcat容器中成功的部署了solr全文检索引擎系统的服务:今天来分享一下solr服务在海量数据的网站中是如何实现数据的检索. 在solr服务中集成IKAnalyzer中文分词器的步骤: ...

  3. Linux下服务器端开发流程及相关工具介绍(C++)

    去年刚毕业来公司后,做为新人,发现很多东西都没有文档,各种工具和地址都是口口相传的,而且很多时候都是不知道有哪些工具可以使用,所以当时就想把自己接触到的这些东西记录下来,为后来者提供参考,相当于一个路 ...

  4. Oracle 的基本操作符

    != 不等于 select empno,ename,job from scott.emp where job!='manager' ^= 不等于 select empno,ename,job from ...

  5. ABP框架 - OData 集成

    文档目录 本节内容: 简介 安装 安装Nuget包 设置模块依赖 配置你的实体 创建控制器 示例 获取实体列表 请求 响应 获取单个实体 请求 响应 获取单个实体及导航属性 请求 响应 查询 请求 响 ...

  6. 标准产品+定制开发:专注打造企业OA、智慧政务云平台——山东森普软件,交付率最高的技术型软件公司

    一.公司简介山东森普信息技术有限公司(以下简称森普软件)是一家专门致力于移动互联网产品.企业管理软件定制开发的技术型企业.公司总部设在全国五大软件园之一的济南齐鲁软件园.森普SimPro是由Simpl ...

  7. x:bind不支持样式文件 或 此Xaml文件必须又代码隐藏类才能使用{x:Bind} 解决办法

    这两天学习UWP开发,发现一个很有趣的问题,就是我题目中的描述的. 我习惯了在ResourceDictionary中写样式文件,但是发现用x:Bind时会有问题 如果是写在Style里,则提示 “x: ...

  8. 【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之集群概念介绍(一)

    集群概念介绍(一)) 白宁超 2015年7月16日 概述:写下本文档的初衷和动力,来源于上篇的<oracle基本操作手册>.oracle基本操作手册是作者研一假期对oracle基础知识学习 ...

  9. js数组学习整理

    原文地址:js数组学习整理 常用的js数组操作方法及原理 1.声明数组的方式 var colors = new Array();//空的数组 var colors = new Array(3); // ...

  10. 走进缓存的世界(三) - Memcache

    系列文章 走进缓存的世界(一) - 开篇 走进缓存的世界(二) - 缓存设计 走进缓存的世界(三) - Memcache 简介 Memcache是一个高性能的分布式内存对象缓存系统,用于动态Web应用 ...