我所理解的正则表达式
学习了半年的正则表达式,也不能说一直学习吧,就是和它一直在打交道,如何用正则表达式解决自己的问题,并且还要考虑如何在匹配大量的文本时去优化它。慢慢的觉得正则已经成为自己的一项技能,逐渐的从一个正则表达式小白变成一个伪精通者。 那么,我不打算详细介绍正则表达式的使用,或者说这篇文章并不是入门教程,所以如果你对正则表达式一无所知或者处于入门阶段,建议你还是先去看看下面这些正则表达式入门的文章。
当然正则的教程很多,不限于此,如果你对正则已经了解了,那么可以开始下面的内容了,文章中可能还会涉及一些效率的问题。 new RegExp 和 // 正则对象创建区别如果写过 Python 的同学,都一定会知道 Python 中可以在字符串前面加个小写的 r ,来表示防止转义。防止转义的意思就是说:str = r"t' 等价于 str = 't',加了 r 会防止 被转义。 为什么要介绍这个,因为这就是 //推荐写法 var regex1 = /w+/g; regex1 // /w+/g //RegExp 写法 var regex2 = new RegExp('w+','g'); regex2 // /w+/g //错误写法 var regex3 = new RegExp('w+','g'); regex3 // /w+/g 你也看出来了,错误写法只能匹配 当然,还有比较奇葩的: var regex4 = new RegExp(/w+/g); regex4 // /w+/g MSDN 上关于 RegExp 的介绍。 那么,如何能像 Python 的 var str1 = 'dws'; str1; // "dws" var str2 = /dws/; str2.source; // "dws" 没错,就是 i、g、m 修饰符这几个修饰符只是针对 JS 来说的,像 Python 中还有 对于 i 表示忽略字母大小写,不是很常用,因为它有很多替代品,比如: 使用 i 需要注意的地方,就是 i 会对正则表达式的每一个字母都忽略大小写,当我们需要部分单词的时候,可以考虑一下 g 表示全局匹配,在印象中,可能很多人会觉得全局匹配就是当使用 match 的时候,把所有符合正则表达式的文本全部匹配出来,这个用途确实很广泛,不过 g 还有其他更有意思的用途,那就是 var str = '1a2b3c4d5e6f',reg = /dwd/g; str.match(reg); //["1a2","3c4","5e6"] 为什么不包括 var str = '1a2b3c4d5e6f',reg = /dwd/g; var a; var arr = []; while(a = reg.exec(str)){ arr.push(a[0]); reg.lastIndex -= 1; } arr //["1a2","2b3","4d5","5e6"] m 表示多行匹配,我发现很多人介绍 m 都只是一行略过,其实关于 m 还是很有意思的。首先,来了解一下单行模式,我们知道 JavaScript 正则表达式中的 多行模式跟 var str = 'na'; /^a/.test(str); //false /^a/m.test(str); //true 有人说,m 没用。其实在某些特殊的格式下,你知道你要匹配的内容会紧接着 rn 或以 rn 结尾,这个时候 m 就非常有用,比如 HTTP 协议中的请求和响应,都是以 rn 划分每一行的,响应头和响应体之间以 rnrn 来划分,我们需要匹配的内容就在开头,通过多行匹配,可以很明显的提高匹配效率。 原理性的东西,我们还是要知道的,万一以后会用到。 (?:) 和 (?=) 区别在正则表达式中,括号不能乱用,因为括号就代表分组,在最终的匹配结果中,会被算入字匹配中,而 (?:) 就是来解决这个问题的,它的别名叫做非捕获分组。 var str = 'Hello world!'; var regex = /Hello (w+)/; regex.exec(str); //["Hello world","world"] var regex2 = /Hello (?:w+)/; regex2.exec(str); //["Hello world"] //replace 也一样 var regex3 = /(?:ab)(cd)/ 'abcd'.replace(regex3,'$1') //"cd" 可以看到 (?:) 并不会把括号里的内容计入到子分组中。 关于 (?=),新手理解起来可能比较困难,尤其是一些很牛逼的预查正则表达式。其实还有个 (?!),不过它和 (?=) 是属于一类的,叫做正向肯定(否定)预查,它还有很多别名比如零宽度正预测先行断言。但我觉得最重要的只要记住这两点,预查和非捕获。 预查的意思就是在之前匹配成功的基础上,在向后预查,看看是否符合预查的内容。正因为是预查,lastIndex 不会改变,且不会被捕获到总分组,更不会被捕获到子分组。 var str = 'Hello world!'; var regex = /Hello (?=w+)/; regex.exec(str); //["Hello "] 和 (?:) 区别是:我习惯的会把匹配的总结果叫做总分组,match 函数返回数组每一项都是总分组,exec 函数的返回数组的第一项是总分组。(?:) 会把括号里的内容计入总分组,(?=) 不会把括号里的内容计入总分组。 说白了,还是强大的 lastIndex 在起作用。(?:) 和 (?=) 差别是有的,使用的时候要合适的取舍。 说了这么多关于 (?=) 的内容,下面来点进阶吧!现在的需求是一串数字表示钱 "10000000",但是在国际化的表示方法中,应该是隔三位有个逗号 "10,000,000",给你一串没有逗号的,替换成有逗号的。 var str = "10000000"; var regex = /d(?=(d{3})+$)/g; str.replace(regex,'$&,'); //"10,000" 我们分析一下 regex,/d(?=(d{3})+$)/g 它是全局 g,实际上它匹配的内容只有一个 d,(?=(d{3})+$) 是预判的内容,之前说过,预判的内容不计入匹配结果,lastIndex 还是停留在 d 的位置。(?=(d{3})+$) 到结尾有至少一组 3 个在一起的数字,才算预判成功。 d = 1 的时候,不满足预判,向后移一位,d = 0,满足预判,replace。 (?!) 前瞻判断(?=) 和 (?!) 叫做正向预查,但往往是正向这个词把我们的思维给束缚住了。正向给人的感觉是只能在正则表达式后面来预判,那么预判为什么不能放在前面呢。下面这个例子也非常有意思。 一个简单密码的验证,要保证至少包含大写字母、小写字母、数字中的两种,且长度 8~20。 如果可以写多个正则,这个题目很简单,思路就是:/^[a-zA-Zd]{8,20}$/ && !(/[a-z]+/) && !(/[A-Z]+/) && !(/d+/),看着眼都花了,好长一串。 下面用 (?!) 前瞻判断来实现: var regex = /^(?![a-z]+$)(?![A-Z]+$)(?!d+$)[a-zA-Zd]{8,12}$/; regex.test('12345678'); //false regex.test('1234567a'); //true 分析一下,因为像 (?!) 预判不消耗 lastIndex,完全可以放到前面进行前瞻。 对 JS 正则不支持 (?<=) 个人看法我们都知道,JS 中的正则表达式是不支持正回顾后发断言的 原因可能是采用的正则引擎不一样导致,既然不支持,那我们也只能通过现有的条件来改进我们所写的正则,下面就说一说我的理解。 对于一个非全局匹配的正则表达式,完全可以通过 那如果是全局匹配呢?又该如何实现 var str = 'a1b2c3d'; //var regex = /(?<=w)dw/g //str.match(regex) => ['1b','2c','3d'] var regex2 = /(?:w)dw/g str.match(regex2); //["a1b","c3d"] 很明显,只通过 这时候,又要拿出强大的 var str = 'a1b2c3d'; var regex = /(?:w)(dw)/g; var m,arr = []; while(m = regex.exec(str)){ arr.push(m[1]); regex.lastIndex --; } arr; //["1b","2c","3d"] 和前面的例子很类似,通过重写 lastIndex 的值,达到模仿 非贪婪与贪婪的问题贪婪出现在 鉴于上面的情况,可以使用 ? 来实现非贪婪匹配。? 在正则表达式中用途很多,正常情况下,它表示前面那个字符匹配 0 或 1 次,就是简化版的 我刚开始写正则的时候,写出来的正则都是贪婪模式的,往往得到的结果和预想的有些偏差,就是因为少了 ? 的原因。 我初入正则的时候,非贪婪模式还给我一种错觉。还是前面的那个例子,被匹配的内容换一下,用 一开始我对于这种情况是不理解的,但仔细想想也对,这本来就应该返回成功。至于如何在第一次尝试匹配失败之后,后面就不再继续匹配,只能通过优化 所以,贪婪是返回最近的一次成功匹配,而不是第一次尝试。 避免回溯失控回溯可以杀死一个正则表达式,这一点都不假。关于正则表达式回溯也很好理解,就是正则引擎发现有两条路可以走时,它会选择其中的一条,把另一条路保存以便回溯时候用。 比如正则 所谓的回溯失控,就是可供选择的路径太多,看一个常见回溯失控的例子,正则 当然你可能会说,自己不会写这样傻的正则表达式。真的吗?我们来看一个匹配 html 标签的正则表达式,/<html>[sS]*?<head>[sS]*?</head>[sS]*?<body>[sS]*?</body>[sS]*?</html> (感觉这样写也很傻)。如果一切都 OK,匹配一个正常的 HTML 页面,工作良好。但是如果不是以 在说到回溯的同时,有时候还是要考虑一下 总结感觉这篇文章写的很乱,东扯西扯的,大概把我这几个月以来所学到的正则表达式知识都写在了这里,当然这并不包括一些基础的知识。我觉得学习正则最主要的还是去练习,只有在实际项目中总结出来的正则经验,才算自己正在掌握的,如果只是简单的扫一眼,时间久了,终究会忘记。共勉! 参考
如何找出文件名为 ".js" 的文件,但要过滤掉 ".min.js" 的文件。 代码如下: 欢迎来我的博客参考代码。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |