交易所开发深圳网站制作,微信小程序注册后怎么使用,济南网页设计培训,陕西建设厅八大员报名官网本文同步自我的博客园#xff1a;http://www.cnblogs.com/hustskyking/ 关于正则表达式#xff0c;网上可以搜到一大片文章#xff0c;我之前也搜集了一些资料#xff0c;并做了排版整理#xff0c;可以看这篇文章http://www.cnblogs.com/hustskyking/archive/2013/06/04/… 本文同步自我的博客园http://www.cnblogs.com/hustskyking/ 关于正则表达式网上可以搜到一大片文章我之前也搜集了一些资料并做了排版整理可以看这篇文章http://www.cnblogs.com/hustskyking/archive/2013/06/04/RegExp.html作为基础入门讲解这篇文章说的十分到位。 记得最开始学习正则是使用 php 做一个爬虫程序。为了获取指定的信息必须用一定的方式把有规律的数据匹配出来而正则是首选。下面是当时写的爬虫程序的一个代码片段 $regdata /font size\3\((?bf[^]*)br \/){0,1}⊙(?bs.{12})\S*\s/;//获取页面
$html file_get_contents(http://www.qnwz.cn/html/daodu/201107/282277.html);
$html iconv(GBK, UTF-8, $html);
if ($html ) { die(hr /出错【错】无法打开《青年文摘》页面hr /);
}//匹配页面信息
preg_match_all($regdata, $html, $mdata);print_r($mdata);当时写代码还真是欢乐多什么都不懂什么都是新知识学起来津津有味。我觉得学习知识一定要把握最基本的原理先把一个知识的大概轮廓搞清楚然后学习怎么去使用他完了就是深入学习了解底层基础实现。很多人解决问题都是靠经验这个当然很重要但如果我们弄懂了一项技术最底层的实现完全可以靠自己的推断分析出问题的根源。我对一些公司的招聘要求特别不满说什么要三年五年Javascript编程经验云云经验当然和时间成正相关但是对于那些没有三年五年工作经验却照样能够解决实际的人呢算是小小的吐槽吧下面进入正题。 一、正则表达式的工作机制 画了一个草图简单的说明了下正则表达式的工作原理。 --------| 编译 |--------|↓
----------------
| 设置开始位置 |←---------
---------------- ↑| |↓ 其 |
---------------- 他 |
| 匹配 回溯 | 路 |
---------------- 径 || |↓ |
---------------- |
| 成功 or 失败 |---------→
----------------你写的任何一个正则直接量或者 RegExp 都会被浏览器编译为一个原生代码程序第一次匹配是从头个字符开始匹配成功时他会查看是否还有其他的路径没有匹配到如果有的话回退到上一次成功匹配的位置然后重复第二步操作不过此时开始匹配的位置lastIndex是上次成功位置加 1.这样说有点难以理解下面写了一个 demo这个 demo 就是实现一个正则表达式的解析引擎因为逻辑和效果的表现都太复杂了所以只做了一个简单的演示 http://qianduannotes.duapp.com/demo/regexp/index.html 如果要深入了解正则表达式的内部原理必须先理解匹配过程的一个基础环节——回溯他是驱动正则的一个基本动力也是性能消耗、计算消耗的根源。 二、回溯 正则表达式中出现最多的是分支和量词上面的 demo 中可以很清楚的看到 hi 和 hello 这两个分支当匹配到第一个字符 h 之后进入 (i|ello) 的分支选择首先是进入 i 分支当 i 分支匹配完了之后再回到分支选择的位置重新选择分支。简单点说分支就是 | 操作符带来的多项选择问题而量词指的是诸如 *, ?, {m,n} 之类的符号正则表达式必须决定何时尝试匹配更多的字符。下面结合回溯详细说说分支和量词。 1. 分支 继续分析上面那个案例。Lalala. Hi, barret. Hello, John.match(/H(i|ello), barret/g),首先会查找 H 字符在第九位找到 H 之后正则子表达式提供了两个选择 (i|ello)程序会先拿到最左边的那个分支进入分支后在第十位匹配到了 i接着匹配下一个字符下一个字符是逗号接着刚才的位置又匹配到了这个逗号然后再匹配下一个依次类推直到完整匹配到整个正则的内容此时程序会在Hi, barret后面做一个标记表示在这里进行了一次成功的匹配。但程序到此并没有结束因为后面加了一个全局参数依然使用这个分支往后匹配很显然到了 Hello 的时候Hi 分支匹配不了了于是程序会回溯到刚才我们做标记的位置并进入第二个分支从做标记的位置重新开始匹配依次循环。 只要正则表达式没有尝试完所有的可选项他就会回溯到最近的决策点也就是上次匹配成功的位置。 2. 量词 量词这个概念特别简单只是在匹配过程中有贪婪匹配和懒惰匹配两种模式结合回溯的概念理解稍微复杂。还是用几个例子来说明。 1) 贪婪 str AB1111BA111BA;
reg /AB[\s\S]BA/;
console.log(str.match(reg));首先是匹配AB遇到了 [\s\S]这是贪婪模式的匹配他会一口吞掉后面所有的字符也就是如果 reg 的内容为 AB[\s\S]那后面的就不用看了直接全部匹配而往后看正则后面还有B字符所以他会先回溯到倒数第一个字符匹配看是否为 B显然倒数第一个字符不是B于是他又接着回溯找到了B字母找到之后就不继续回溯了而是往后继续匹配此刻匹配的是字符A程序发现紧跟B后的字母确实是A那此时匹配就结束了。如果没有看明白可以再读读下面这个图 REG: /AB[\s\S]BA/
MATCH: A 匹配第一个字符AB 匹配第二个字符AB1111BA111BA [\s\S] 贪婪吞并所有字符AB1111BA111BA 回溯匹配字符BAB1111BA111B 找到字符B继续匹配AAB1111BA111BA 找到字符A匹配完成停止匹配2) 懒惰非贪婪 str AB1111BA111BA;
reg /AB[\s\S]?BA/;
console.log(str.match(reg));与上面不同的是reg 中多了一个 ? 号此时的匹配模式为懒惰模式也叫做非贪婪匹配。此时的匹配流程是先匹配AB遇到[\s\S]?程序尝试跳过并开始匹配后面的字符B往后查看的时候发现是数字1不是要匹配的内容继续往后匹配知道遇到字符B然后匹配A发现紧接着B后面就有一个A于是宣布匹配完成停止程序。 REG: /AB[\s\S]BA/
MATCH: A 匹配第一个字符AB 匹配第二个字符AB [\s\S]? 非贪婪跳过并开始匹配BAB1 不是B回溯继续匹配AB11 不是B回溯继续匹配AB111 不是B回溯继续匹配AB1111 不是B回溯继续匹配AB1111B 找到字符B继续匹配AAB1111BA 找到字符A匹配完成停止匹配如果匹配的内容是 AB1111BA那贪婪和非贪婪方式的正则是等价的但是内部的匹配原理还是有区别的。为了高效运用正则必须搞清楚使用正则时会遇到那些性能消耗问题。 三、逗比的程序 //去测试下这句代码
TTTTTTTT.match(/(TT)K/);
//然后把前面的T重复次数改成30
//P.S:小心风扇狂转CPU暴涨我们来分析下上面这段代码上面使用的都是贪婪模式那么他会这样做 REG: (TT)K
MATCH: ①第一个T匹配前7个T第二个T匹配最后一个T没找到K宣布失败回溯到最开始位置②第一个T匹配前6个T第二个T匹配最后两个T没找到K宣布失败回溯到最开始位置③...... 接着还会考虑(TT)后面的 号接着另一轮的尝试。⑦......这段程序并不会智能的去检测字符串中是否存在 K如果匹配失败他会选择其他的匹配方式路径去匹配从而造成疯狂的回溯和重新匹配结果可想而知。这是回溯失控的典型例子。 四、前瞻和反向引用 1. 前瞻和引用 前瞻有两种一种是负向前瞻JS中使用 (?!xxx) 来表示他的作用是对后面要匹配的内容做一个预判断如果后面的内容是xxx则此段内容匹配失败跳过去重新开始匹配。另一种是正向前瞻(?xxx)匹配方式和上面相反还有一个长的类似的是 (?:xxx),这个是匹配xxx他是非捕获性分组匹配即匹配的内容不会创建反向引用。具体内容可以去文章开头提到的文档中查看。 反向引用这个在 replace 中用的比较多在 replace 中 字符替换文本$1、$2、...、$99与 regexp 中的第 1 到第 99 个子表达式相匹配的文本。$与 regexp 相匹配的子串。$位于匹配子串左侧的文本。$位于匹配子串右侧的文本。$$直接量符号。 而在正则表达中主要就是 \1, \2 之类的数字引用。前瞻和反向引用使用恰当可以大大的减少正则对资源的消耗。举个例子来简单说明下这几个东西 问题使用正则匹配过滤后缀名为 .css 和 .js 的文件。如test.wow.js test.wow.css test.js.js等等。有人会立马想到使用负向前瞻即 //过滤js文件
/(?!.\.js$).*/.exec(test.wow.js)//过滤js和css文件
/(?!.\.js$|.\.css$).*/.exec(test.wow.js)
/(?!.\.js$|.\.css$).*/.exec(test.wow.html)但是你自己去测试下拿到的结果是什么。匹配非js和非css文件可以拿到正确的文件名但是我们期望这个表达式对js和css文件的匹配结果是null上面的表达式却做不到。问题是什么因为(?!xxx)和(?xxx)都会消耗字符在做预判断的时候把 .js 和 .css 给消耗了所以这里我们必须使用非捕获模式。 /(?:(?!.\.js$|.\.css$).)*/.exec(test.wow.html);
/(?:(?!.\.js$|.\.css$).)*/.exec(test.wow.js);我们来分析下这个正则 (?:(?!.\.js$|.\.css$).)*
--- ---------------- -| | | ----------------------↓ |
非捕获内部只有一个占位字符|↓负向前瞻以.js和.css结尾的字符串最后一个星号是贪婪匹配直接吞掉全部字符。 这里讲的算是有点复杂了不过在稍复杂的正则中这些都是很基础的东西了想在这方面提高的童鞋可以多研究下。 2. 原子组 JavaScript的正则算是比较弱的他没有分组命名、递归、原子组等功能特别强的匹配模式不过我们可以利用一些组合方式达到自己的目的。上面的例子中我们实际上用正则实现了一个或和与的功能上面的例子体现的还不是特别明显再写个例子来展示下 str1 我(wo)叫(jiao)李(li)靖(jing);
str2 李(li)靖(jing)我(wo)叫(jiao);
reg /(?.*?我)(?.*?叫)(?.*?李)(?.*?靖)/;
console.log(reg.test(str1)); //true
console.log(reg.test(str2)); //true不管怎么打乱顺序只要string中包含“我”“是”“李”“靖”这四个字结果都是true。 类似(?xxx)\1就相当于一个原子组原子组的作用就是消除回溯只要是这种模式匹配过的地方回溯时都不会到这里和他之前的地方。上面的程序TTTTTTTT.match(/(TT)K/);可以通过原子组的方式处理 TTTTTTTT.match(/(?(TT))\2K/);如此便能彻底消除回溯失控问题。 五、小结 关于正则的学习重点是要多练习多实践并且多尝试用不同的方案去解决一个正则问题一个很典型的例子去除字符串首尾的空白尝试用5-10种不同的正则去测试并思考哪些方式的效率最高为什么通过这一连串的思考可以带动你学习的兴趣也会让你成长的比较快~