$ ( ) * + . ? [ \ ^ {
在文本中遇到 这几种特殊字符想转为文本,需要通过反斜杠\
转义:
/\$ \( \) \* \+ \. \? \[ \\ \^ \{/g
也许你会问到 -
减号符,}
右花括号,]
右中括号为何不在内?首先减号符是在[]
中的,在前面没遇到转义的[
左方括号时,减号符及右方括号-]
是当普通文本处理,无需转义,而花括号也如此
Regexp : /\{\w+}\[hello-world]/
Text : {abc}[hello-world] hey! hey! hey!
(2) 再次匹配先前匹配的文本
如果需要匹配一个 yyyy-mm-dd
格式的日期,其中月份和日期都是年份的个十位
/\b\d\d(\d\d)-\1-\1\b/
反斜杠\1-9
可以得到前面分组(\d\d)
捕获到的结果,如果是10-99呢?那就\10
至\99
Regexp : /\b\d\d(\d\d)-\1-\1\b/
Text : 2008-08-08
单词边界这个概念我开始比较抽象,也花了点时间去实践到底是啥,所以在这里作为一个案例来说说。
在单词边界匹配的位置,单词字符后面或前面不与另一个单词字符直接相邻。 不太明白,还是看实例吧!
"That dang-tootin' #!@#$ varmint's cost me $199.95!".replace(/\b/g,function(){ console.log(arguments) });
//output ["", 0, "That dang-tootin' #!@#$ varmint's cost me $199.95!"] ["", 4, "That dang-tootin' #!@#$ varmint's cost me $199.95!"] ["", 5, "That dang-tootin' #!@#$ varmint's cost me $199.95!"] ["", 9, "That dang-tootin' #!@#$ varmint's cost me $199.95!"] ["", 10, "That dang-tootin' #!@#$ varmint's cost me $199.95!"] ["", 16, "That dang-tootin' #!@#$ varmint's cost me $199.95!"] ["", 24, "That dang-tootin' #!@#$ varmint's cost me $199.95!"] ["", 31, "That dang-tootin' #!@#$ varmint's cost me $199.95!"] ["", 32, "That dang-tootin' #!@#$ varmint's cost me $199.95!"] ["", 33, "That dang-tootin' #!@#$ varmint's cost me $199.95!"] ["", 34, "That dang-tootin' #!@#$ varmint's cost me $199.95!"] ["", 38, "That dang-tootin' #!@#$ varmint's cost me $199.95!"] ["", 39, "That dang-tootin' #!@#$ varmint's cost me $199.95!"] ["", 41, "That dang-tootin' #!@#$ varmint's cost me $199.95!"] ["", 43, "That dang-tootin' #!@#$ varmint's cost me $199.95!"] ["", 46, "That dang-tootin' #!@#$ varmint's cost me $199.95!"] ["", 47, "That dang-tootin' #!@#$ varmint's cost me $199.95!"] ["", 49, "That dang-tootin' #!@#$ varmint's cost me $199.95!"]
通过控制台输出,我们可以发现\b
匹配的位置如下:
细心的你一定会发现数字的开始,小数点的左右都会有单词边界,这意味着单词边界不仅仅是英文字母,还包括数字。
\b`属于匹配位置的元字符,一般作占位作用,而不被捕获,同属于匹配位置的还有匹配行起始位`^`和行结束位`$
24小时制可以分为三段:
00 - 09
10 - 19
20 - 23
先匹配第一阶段: /[0][0-9]/
再接着匹配第二阶段:/[01][0-9]/
第三阶段以此类推?/[012][0-9]/
明显不合适,因为不可能出现大于23以上的数字,那么只能开分支了 /[01][0-9]|2[0-3]/
也许你会说,00-09如果要匹配“没有补零”的情况呢?(即:0,1,2,3,4,5...)
我们可以借助一下量词?
匹配 /[01]?[0-9]|2[0-3]/
Regexp : /([01]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]
/
我们在一些场景里需要将7654321输出成7,654,321这样的格式,这就是千分位,用正则表达式去处理的话,关键是获取位置,那么首先想到的就是要利用非单词边界\B,下面这条正则是能成功取得千分位的位置的:
/\B(?=(\d{3})+(?!\d))/g
先将这个正则分解成三部分:
1 )、 /\B(?=\d)/
这是\B
是为了防止出现,123
起始位置被匹配的问题,(?=\d)
是非单词边界后紧跟数字
2 )、 尝试一下8位数的数字: '12345678' 在 /\B(?=(\d{3})+)/
匹配得到什么结果呢?
'12345678'.replace(/\B(?=(\d{3})+)/g,function(){console.log(arguments);return '|'})
["", "567", 1, "12345678"] ["", "678", 2, "12345678"] ["", "456", 3, "12345678"] ["", "567", 4, "12345678"] ["", "678", 5, "12345678"] "1|2|3|4|5|678"
首先符合非单词边界\B
的有1,2,3,4,5,6,7的右边位置,而后面也同样紧跟数字2,3,4,5,6,7
其次符合\d{3}
的有234,345,456,567,678,但后面跟个加号+
结果就不一样了
那为什么会得到567,678,456,567,678,这样奇怪的匹配?原理如下:
1. 匹配`\B`第1个非单词边界 `1`的右边位置,则后面(\d{3})+的结果为:234、567,8后面无法补齐3位,匹配得到567 2. 匹配`\B`第2个非单词边界 `2`的右边位置,则后面(\d{3})+的结果为:345、678,匹配得到678 3. 匹配`\B`第3个非单词边界 `3`的右边位置,则后面(\d{3})+的结果为:456、78后面无法补齐3位,匹配得到456 4. 匹配`\B`第4个非单词边界 `4`的右边位置,则后面(\d{3})+的结果为:567、8后面无法补齐3位,匹配得到567 5. 匹配`\B`第5个非单词边界 `5`的右边位置,则后面(\d{3})+的结果为:678 6. 匹配`\B`第6个非单词边界 `6`的右边位置,但78无法补齐3位, 7. 同6 8. 最终小括号分组匹配得到的分别是:567,678,456,567,678
3)、最后 (?!\d) 是前面匹配成功后跟的非数字,那连起来就是:
12,345,678 1. 匹配`\B`第1个非单词边界 `1`的右边位置,则后面(\d{3})+的结果为:234、567,后面跟着8,不匹配 2. 匹配`\B`第2个非单词边界 `2`的右边位置,则后面(\d{3})+的结果为:345、678,后面跟着非数字,位置匹配成功 3. 匹配`\B`第3个非单词边界 `3`的右边位置,则后面(\d{3})+的结果为:456,后面跟着7、8不匹配 4. 匹配`\B`第4个非单词边界 `4`的右边位置,则后面(\d{3})+的结果为:567,后面跟着8,不匹配 5. 匹配`\B`第5个非单词边界 `5`的右边位置,则后面(\d{3})+的结果为:678,后面跟着非数字,位置匹配成功 6. 最终得到得到可插入逗号的位置为2,5
在上一篇介绍千分位的时候,就有用到重复分组,这是一个很容易理解错误的点,至少我开始的时候是理解错误的。
假定用这个正则去匹配 1234567890
,开始我以为分组小括号(\d\d\d)
的最终结果是 123
,456
,789
都能拿到,但结果却只有789
重复分组的匹配在每次引擎退出该分组的时候被捕获,并会覆盖该分组在之前匹配的任何文本
模拟一下引擎工作的步骤:
1. 第一次匹配,捕获到 `123`,退出分组 2. 第二次匹配,捕获到 `456`, 覆盖上一次捕获的`123`,退出分组 3. 第三次匹配,捕获到 `789`,覆盖上一次捕获的`456`,退出分组 4. 退出重复分组,结束
因为重复分组最后一次循环存储的是789,另外两次分组匹配,也就是123,456是无法被获取的。
如果想要获得所有结果,就要把重复匹配放进分组中 /((\d\d\d){3})/
...
中的内容/(.*?)<\/p>/g.exec('
Hello,world
Hello,Janking
')
运行结果
[ "Hello,world
", "Hello,world", index: 0, input: "Hello,world
Hello,Janking
" ]
假如没有?
,而匹配结果就会差很远,不信你看!
/(.*)<\/p>/g.exec('
Hello,world
Hello,Janking
')
运行结果
[ "Hello,world
Hello,Janking
", "Hello,worldHello,Janking", index: 0, input: "
Hello,world
Hello,Janking
" ]
缺少了问号?
,结果把中间的
也匹配进来了,为什么一个字符不同,匹配结果就差异那么大?这里涉及到正则表达式中比较重要的概念:贪婪匹配,懒惰匹配,回溯
贪婪匹配
属于贪婪模式的量词,也叫做匹配优先量词,包括:{m,n}
,{m,}
,?
,*
和 +
。
惰性匹配
在匹配优先量词后加上?
,即变成属于惰性匹配的量词,也叫做忽略优先量词,包括:{m,n}?
,{m,}?
,??
,*?
和 +?
。
回溯
当前前面分支/重复匹配成功后,没有多余的文本可被正则后半部分匹配时,会产生回溯
用一个简单的例子来解释一下贪婪匹配和惰性匹配!
贪婪 : /\d+\b/
惰性 : /d+?\b/
文本 : 1234a
贪婪正则匹配 1234a
时的过程是这样的:
1. \d+ 匹配得到 1234 2. \b 却匹配失败(\b 是分词边界匹配,用来获取位置,而不是文本,上一节有讲到) 4. 这个时候,\d+会尝试回吐一个字符,即匹配结果为 123 ,可\b还是匹配失败! 5. 那就继续回吐,一直到 1,还是匹配失败,那么这个正则就整体匹配失败了 6. 这个回吐匹配结果的过程就是回溯
惰性正则匹配 1234a
时的过程是这样的:
1. \d+? 首先匹配,结果是1 ,紧接着 \b 匹配失败 2. 那就 \d+? 继续匹配,结果是 12 ,紧接着 \b 还是匹配失败 3. \d+? 一直匹配到1234,紧接着的 \b 依然匹配失败 4. 结果整个正则匹配不成功
通过这两个例子的比较,相信你会猜到回溯会影响匹配速度,回溯的过程慢那是相对那些DFA引擎。
而JS的正则引擎是NFA(非确定型有限自动机),匹配慢,编译快。