导读
你有没有在搜刮文本的时刻挖空心思, 试了一个又一个表达式, 照样不可.
你有没有在表单考证的时刻, 只是做做模样(只需不为空就好), 然后烧香拜佛, 虔敬祷告, 万万不要失足.
你有没有在应用sed 和 grep 敕令的时刻, 觉得稀里糊涂, 明显应当支撑的元字符, 却就是婚配不到.
以至, 你压根没遇到过上述状况, 你只是一遍又一遍的挪用 replace 罢了 (把非搜刮文本悉数替代为空, 然后就只剩搜刮文本了), 面临别人家的简约高效的语句, 你只能在心中叫嚣, replace 大法好.
为何要学正则表达式. 有位网友这么说: 江湖传说里, 递次员的正则表达式和大夫的处方, 羽士的鬼符齐名, 曰: 平常人看不懂的三件神器. 这个传说起码向我们透露了两点信息: 一是正则表达式很牛, 能和大夫的处方, 羽士的鬼符齐名, 并被人人提起, 可见其江湖职位. 二是正则表达式很难, 这也从正面说清楚明了, 如果你可以闇练的控制并应用它, 在装逼的路上, 你将方兴未艾 (别问我中天是谁……) !
明显, 有关正则表达的引见, 不必我多言. 这里就借助 Jeffrey Friedl 的《通晓正则表达式》一书的序文正式抛个砖.
“如果排列盘算机软件范畴的庞大发明, 我置信相对不会凌驾二十项, 在这个名单当中, 固然应当包括分组交流收集, Web, Lisp, 哈希算法, UNIX, 编译手艺, 关联模子, 面向对象, XML这些赫赫有名的家伙, 而正则表达式也相对不应当被遗漏.
对许多实际事情而言, 正则表达式简直是灵丹妙药, 可以成百倍的进步开辟效力和递次质量, 正则表达式在生物信息学和人类基因图谱的研讨中所发挥的关键作用, 更是被传为佳话. CSDN的创始人蒋涛先生在从前开辟专业软件产品时, 就曾体验过这一东西的庞大威力, 而且一向印象深入.”
因而, 我们没有来由不去相识正则表达式, 以至是闇练控制并应用它.
本文以正则基础语法开篇, 连系细致实例, 逐渐解说正则表达式婚配道理. 代码实例应用言语包括 js, php, python, java(因有些婚配情势, js并未支撑, 需要借助其他言语解说). 内容包括初阶妙技和高阶妙技, 合适新手进修和进阶. 本文力图简朴通俗易懂, 同时为求周全, 触及学问较多, 总计12k字, 篇幅较长, 请耐烦浏览, 若有浏览障碍请实时联络我.
回忆历史
要论正则表达式的渊源, 最早可以追溯至对人类神经系统怎样事情的初期研讨. Warren McCulloch 和 Walter Pitts 这两位神经大咖 (神经生理学家) 研讨出一种数学体式格局来形貌这些神经收集.
1956 年, 一名叫 Stephen Kleene 的数学家在 McCulloch 和 Pitts 初期事情的基础上, 宣布了一篇标题为”神经网事宜的示意法”的论文, 引入了正则表达式的观点.
随后, 发明可以将这一事情应用于应用 Ken Thompson 的盘算搜刮算法的一些初期研讨中. 而 Ken Thompson 又是 Unix 的重要发明人. 因而半个世纪之前的Unix 中的 qed 编辑器(1966 qed编辑器问世) 成了第一个应用正则表达式的应用递次.
至此以后, 正则表达式成为众所周知的文本处置惩罚东西, 险些各大编程言语都以支撑正则表达式作为卖点, 固然 JavaScript 也不破例.
正则表达式的定义
正则表达式是由平常字符和特别字符(也叫元字符或限定符)构成的笔墨模板. 以下就是简朴的婚配一连数字的正则表达式:
/[0-9]+/
/\d+/
“d” 就是元字符, 而 “+” 则是限定符.
元字符
元字符 | 形貌 |
---|---|
. | 婚配除换行符以外的恣意字符 |
\d | 婚配数字, 等价于字符组[0-9] |
\w | 婚配字母, 数字, 下划线或汉字 |
\s | 婚配恣意的空缺符(包括制表符,空格,换行等) |
\b | 婚配单词最先或完毕的位置 |
^ | 婚配行首 |
$ | 婚配行尾 |
反义元字符
元字符 | 形貌 |
---|---|
\D | 婚配非数字的恣意字符, 等价于[^0-9] |
\W | 婚配除字母,数字,下划线或汉字以外的恣意字符 |
\S | 婚配非空缺的恣意字符 |
\B | 婚配非单词最先或完毕的位置 |
[^x] | 婚配除x以外的恣意字符 |
可以看出正则表达式严厉辨别大小写.
重复限定符
限定符共有6个, 假定重复次数为x次, 那末将有以下划定规矩:
限定符 | 形貌 |
---|---|
* | x>=0 |
+ | x>=1 |
? | x=0 or x=1 |
{n} | x=n |
{n,} | x>=n |
{n,m} | n<=x<=m |
字符组
[…] 婚配中括号内字符之一. 如: [xyz] 婚配字符 x, y 或 z. 如果中括号中包括元字符, 则元字符降级为平常字符, 不再具有元字符的功用, 如 [+.?] 婚配 加号, 点号或问号.
消除性字符组
[^…] 婚配任何未列出的字符,. 如: [^x] 婚配除x以外的恣意字符.
多选组织
| 就是或的意义, 示意二者中的一个. 如: a|b 婚配a或许b字符.
括号
括号 常常使用来界定重复限定符的局限, 以及将字符分组. 如: (ab)+ 可以婚配abab..等, 个中 ab 就是一个分组.
转义字符
即转义字符, 平常 * + ? | { [ ( ) ] }^ $ . # 和 空缺 这些字符都需要转义.
操纵符的运算优先级
转义符
(), (?:), (?=), [] 圆括号或方括号
*, +, ?, {n}, {n,}, {n,m} 限定符
^, $ 位置
| “或” 操纵
测试
我们来测试下上面的学问点, 写一个婚配手机号码的正则表达式, 以下:
(\+86)?1\d{10}
① “\+86” 婚配文本 “+86”, 背面接元字符问号, 示意可婚配1次或0次, 合起来示意 “(\+86)?” 婚配 “+86” 或许 “”.
② 平常字符”1″ 婚配文本 “1”.
③ 元字符 “\d” 婚配数字0到9, 区间量词 “{10}” 示意婚配 10 次, 合起来示意 “\d{10}” 婚配一连的10个数字.
以上, 婚配效果以下:
润饰符
javaScript中正则表达式默许有以下五种润饰符:
g (全文查找), 如上述截图, 实际上就开启了全文查找情势.
i (疏忽大小写查找)
m (多行查找)
y (ES6新增的粘连润饰符)
u (ES6新增)
常常使用的正则表达式
汉字: ^[u4e00-u9fa5]{0,}$
Email: ^w+([-+.]w+)*@w+([-.]w+)*\.w+([-.]w+)*$
URL: ^https?://([w-]+.)+[w-]+(/[w-./?%&=]*)?$
手机号码: ^1d{10}$
身份证号: ^(d{15}|d{17}(d|X))$
中国邮政编码: [1-9]d{5}(?!d) (邮政编码为6位数字)
暗码考证
暗码考证是罕见的需求, 平常来说, 通例暗码大抵会满足规律: 6-16位, 数字, 字母, 字符起码包括两种, 同时不能包括中文和空格. 以下就是通例暗码考证的正则形貌:
var reg = /(?!^[0-9]+$)(?!^[A-z]+$)(?!^[^A-z0-9]+$)^[^\s\u4e00-\u9fa5]{6,16}$/;
正则的几人人族
正则表达式分类
在 linux 和 osx 下, 罕见的正则表达式, 起码有以下三种:
基础的正则表达式( Basic Regular Expression 又叫 Basic RegEx 简称 BREs )
扩大的正则表达式( Extended Regular Expression 又叫 Extended RegEx 简称 EREs )
Perl 的正则表达式( Perl Regular Expression 又叫 Perl RegEx 简称 PREs )
正则表达式比较
字符 | 申明 | Basic RegEx | Extended RegEx | python RegEx | Perl regEx |
---|---|---|---|---|---|
转义 | |||||
^ | 婚配行首,比如’^dog’婚配以字符串dog开首的行(注重:awk 指令中,’^’则是婚配字符串的最先) | ^ | ^ | ^ | ^ |
$ | 婚配行尾,比如:’^、dog$’ 婚配以字符串 dog 为末端的行(注重:awk 指令中,’$’则是婚配字符串的末端) | $ | $ | $ | $ |
^$ | 婚配空行 | ^$ | ^$ | ^$ | ^$ |
^string$ | 婚配行,比如:’^dog$’婚配只含一个字符串 dog 的行 | ^string$ | ^string$ | ^string$ | ^string$ |
< | 婚配单词,比如:'<frog’ (等价于’bfrog’),婚配以 frog 开首的单词 | < | < | 不支撑 | 不支撑(但可以应用b来婚配单词,比如:’bfrog’) |
> | 婚配单词,比如:’frog>’(等价于’frogb ‘),婚配以 frog 末端的单词 | > | > | 不支撑 | 不支撑(但可以应用b来婚配单词,比如:’frogb’) |
<x> | 婚配一个单词或许一个特定字符,比如:'<frog>’(等价于’bfrogb’)、'<G>’ | <x> | <x> | 不支撑 | 不支撑(但可以应用b来婚配单词,比如:’bfrogb’ |
() | 婚配表达式,比如:不支撑’(frog)’ | 不支撑(但可以应用,如:dog | () | () | () |
婚配表达式,比如:不支撑’(frog)’ | 不支撑(同()) | 不支撑(同()) | 不支撑(同()) | ||
? | 婚配前面的子表达式 0 次或 1 次(等价于{0,1}),比如:where(is)?能婚配”where” 以及”whereis” | 不支撑(同?) | ? | ? | ? |
? | 婚配前面的子表达式 0 次或 1 次(等价于'{0,1}’),比如:’whereis? ‘能婚配 “where”以及”whereis” | ? | 不支撑(同?) | 不支撑(同?) | 不支撑(同?) |
? | 当该字符紧跟在任何一个其他限制符(*, +, ?, {n},{n,}, {n,m}) 背面时,婚配情势黑白贪欲的。非贪欲情势只管少的婚配所搜刮的字符串,而默许的贪欲情势则只管多的婚配所搜刮的字符串。比如,关于字符串 “oooo”,’o+?’ 将婚配单个”o”,而 ‘o+’ 将婚配一切 ‘o’ | 不支撑 | 不支撑 | 不支撑 | 不支撑 |
. | 婚配除换行符(’n’)以外的恣意单个字符(注重:awk 指令中的句点能婚配换行符) | . | .(如果要婚配包括“n”在内的任何一个字符,请应用: [sS] | . | .(如果要婚配包括“n”在内的任何一个字符,请应用:’ [.n] ‘ |
* | 婚配前面的子表达式 0 次或屡次(等价于{0, }),比如:zo* 能婚配 “z”以及 “zoo” | * | * | * | * |
+ | 婚配前面的子表达式 1 次或屡次(等价于'{1, }’),比如:’whereis+ ‘能婚配 “whereis”以及”whereisis” | + | 不支撑(同+) | 不支撑(同+) | 不支撑(同+) |
+ | 婚配前面的子表达式 1 次或屡次(等价于{1, }),比如:zo+能婚配 “zo”以及 “zoo”,但不能婚配 “z” | 不支撑(同\+) | + | + | + |
{n} | n 必需是一个 0 或许正整数,婚配子表达式 n 次,比如:zo{2}能婚配 | 不支撑(同\{n\}) | {n} | {n} | {n} |
{n,} | “zooz”,但不能婚配 “Bob”n 必需是一个 0 或许正整数,婚配子表达式大于即是 n次,比如:go{2,} | 不支撑(同\{n,\}) | {n,} | {n,} | {n,} |
{n,m} | 能婚配 “good”,但不能婚配 godm 和 n 均为非负整数,个中 n <= m,起码婚配 n 次且最多婚配 m 次 ,比如:o{1,3}将配”fooooood” 中的前三个 o(请注重在逗号和两个数之间不能有空格) | 不支撑(同\{n,m\}) | {n,m} | {n,m} | {n,m} |
x l y | 婚配 x 或 y | 不支撑(同x l y | x l y | x l y | x l y |
[0-9] | 婚配从 0 到 9 中的恣意一个数字字符(注重:要写成递增) | [0-9] | [0-9] | [0-9] | [0-9] |
[xyz] | 字符鸠合,婚配所包括的恣意一个字符,比如:'[abc]’可以婚配”lay” 中的 ‘a’(注重:如果元字符,比如:. *等,它们被放在[ ]中,那末它们将变成一个平常字符) | [xyz] | [xyz] | [xyz] | [xyz] |
[^xyz] | 负值字符鸠合,婚配未包括的恣意一个字符(注重:不包括换行符),比如:'[^abc]’ 可以婚配 “Lay” 中的’L’(注重:[^xyz]在awk 指令中则是婚配未包括的恣意一个字符+换行符) | [^xyz] | [^xyz] | [^xyz] | [^xyz] |
[A-Za-z] | 婚配大写字母或许小写字母中的恣意一个字符(注重:要写成递增) | [A-Za-z] | [A-Za-z] | [A-Za-z] | [A-Za-z] |
[^A-Za-z] | 婚配除了大写与小写字母以外的恣意一个字符(注重:写成递增) | [^A-Za-z] | [^A-Za-z] | [^A-Za-z] | [^A-Za-z] |
\d | 婚配从 0 到 9 中的恣意一个数字字符(等价于 [0-9]) | 不支撑 | 不支撑 | \d | \d |
\D | 婚配非数字字符(等价于 1) | 不支撑 | 不支撑 | \D | \D |
\S | 婚配任何非空缺字符(等价于2) | 不支撑 | 不支撑 | \S | \S |
\s | 婚配任何空缺字符,包括空格、制表符、换页符等等(等价于[ fnrtv]) | 不支撑 | 不支撑 | \s | \s |
\W | 婚配任何非单词字符 (等价于3) | \W | \W | \W | \W |
\w | 婚配包括下划线的任何单词字符(等价于[A-Za-z0-9_]) | \w | \w | \w | \w |
\B | 婚配非单词边境,比如:’erB’ 能婚配 “verb” 中的’er’,但不能婚配”never” 中的’er’ | \B | \B | \B | \B |
\b | 婚配一个单词边境,也就是指单词和空格间的位置,比如: ‘erb’ 可以婚配”never” 中的 ‘er’,但不能婚配 “verb” 中的’er’ | \b | \b | \b | \b |
\t | 婚配一个横向制表符(等价于 x09和 cI) | 不支撑 | 不支撑 | \t | \t |
\v | 婚配一个垂直制表符(等价于 x0b和 cK) | 不支撑 | 不支撑 | \v | \v |
\n | 婚配一个换行符(等价于 x0a 和cJ) | 不支撑 | 不支撑 | \n | \n |
\f | 婚配一个换页符(等价于x0c 和cL) | 不支撑 | 不支撑 | \f | \f |
\r | 婚配一个回车符(等价于 x0d 和cM) | 不支撑 | 不支撑 | \r | \r |
\ | 婚配转义字符自身”” | \ | \ | \ | \ |
cx | 婚配由 x 指明的控制字符,比如:cM婚配一个Control-M 或回车符,x 的值必需为A-Z 或 a-z 之一,不然,将 c 视为一个原义的 ‘c’ 字符 | 不支撑 | 不支撑 | cx | |
xn | 婚配 n,个中 n 为十六进制转义值。十六进制转义值必需为一定的两个数字长,比如:’x41′ 婚配 “A”。’x041′ 则等价于’x04′ & “1”。正则表达式中可以应用 ASCII 编码 | 不支撑 | 不支撑 | xn | |
num | 婚配 num,个中 num是一个正整数。示意对所猎取的婚配的援用 | 不支撑 | num | num | |
[:alnum:] | 婚配任何一个字母或数字([A-Za-z0-9]),比如:'[[:alnum:]] ‘ | [:alnum:] | [:alnum:] | [:alnum:] | [:alnum:] |
[:alpha:] | 婚配任何一个字母([A-Za-z]), 比如:’ [[:alpha:]] ‘ | [:alpha:] | [:alpha:] | [:alpha:] | [:alpha:] |
[:digit:] | 婚配任何一个数字([0-9]),比如:'[[:digit:]] ‘ | [:digit:] | [:digit:] | [:digit:] | [:digit:] |
[:lower:] | 婚配任何一个小写字母([a-z]), 比如:’ [[:lower:]] ‘ | [:lower:] | [:lower:] | [:lower:] | [:lower:] |
[:upper:] | 婚配任何一个大写字母([A-Z]) | [:upper:] | [:upper:] | [:upper:] | [:upper:] |
[:space:] | 任何一个空缺字符: 支撑制表符、空格,比如:’ [[:space:]] ‘ | [:space:] | [:space:] | [:space:] | [:space:] |
[:blank:] | 空格和制表符(横向和纵向),比如:'[[:blank:]]’ó'[stv]’ | [:blank:] | [:blank:] | [:blank:] | [:blank:] |
[:graph:] | 任何一个可以看得见的且可以打印的字符(注重:不包括空格和换行符等),比如:'[[:graph:]] ‘ | [:graph:] | [:graph:] | [:graph:] | [:graph:] |
[:print:] | 任何一个可以打印的字符(注重:不包括:[:cntrl:]、字符串完毕符’0’、EOF 文件完毕符(-1), 但包括空格标记),比如:'[[:print:]] ‘ | [:print:] | [:print:] | [:print:] | [:print:] |
[:cntrl:] | 任何一个控制字符(ASCII 字符集合的前 32 个字符,即:用十进制示意为从 0 到31,比如:换行符、制表符等等),比如:’ [[:cntrl:]]’ | [:cntrl:] | [:cntrl:] | [:cntrl:] | [:cntrl:] |
[:punct:] | 任何一个标点标记(不包括:[:alnum:]、[:cntrl:]、[:space:]这些字符集) | [:punct:] | [:punct:] | [:punct:] | [:punct:] |
[:xdigit:] | 任何一个十六进制数(即:0-9,a-f,A-F) | [:xdigit:] | [:xdigit:] | [:xdigit:] | [:xdigit:] |
注重
js中支撑的是EREs.
当应用 BREs ( 基础正则表达式 ) 时,必需在以下这些标记(?,+,|,{,},(,))前加上转义字符 .
上述[[:xxxx:]] 情势的正则表达式, 是php中内置的通用字符簇, js中并不支撑.
linux/osx下常常使用敕令与正则表达式的关联
我曾尝试在 grep 和 sed 敕令中誊写正则表达式, 常常发明不能应用元字符, 而且有时刻需要转义, 有时刻不需要转义, 一直不能摸清它的规律. 如果正好你也有一样的疑心, 那末请往下看, 置信应当能有所收成.
grep , egrep , sed , awk 正则表达式特征
grep 支撑:BREs、EREs、PREs 正则表达式
grep 指令后不跟任何参数, 则示意要应用 “BREs”
grep 指令后跟 ”-E” 参数, 则示意要应用 “EREs”
grep 指令后跟 “-P” 参数, 则示意要应用 “PREs”
egrep 支撑:EREs、PREs 正则表达式
egrep 指令后不跟任何参数, 则示意要应用 “EREs”
egrep 指令后跟 “-P” 参数, 则示意要应用 “PREs”
sed 支撑: BREs、EREs
sed 指令默许是应用 “BREs”
sed 指令后跟 “-r” 参数 , 则示意要应用“EREs”
awk 支撑 EREs, 而且默许应用 “EREs”
正则表达式初阶妙技
贪欲情势与非贪欲情势
默许状况下, 一切的限定词都是贪欲情势, 示意只管多的去捕捉字符; 而在限定词后增添?, 则黑白贪欲情势, 示意只管少的去捕捉字符. 以下:
var str = "aaab",
reg1 = /a+/, //贪欲情势
reg2 = /a+?/;//非贪欲情势
console.log(str.match(reg1)); //["aaa"], 由因而贪欲情势, 捕捉了一切的a
console.log(str.match(reg2)); //["a"], 由于黑白贪欲情势, 只捕捉到第一个a
实际上, 非贪欲情势异常有用, 特别是当婚配html标签时. 比如婚配一个配对涌现的div, 计划一能够会婚配到许多的div标签对, 而计划二则只会婚配一个div标签对.
var str = "<div class='v1'><div class='v2'>test</div><input type='text'/></div>";
var reg1 = /<div.*<\/div>/; //计划一,贪欲婚配
var reg2 = /<div.*?<\/div>/;//计划二,非贪欲婚配
console.log(str.match(reg1));//"<div class='v1'><div class='v2'>test</div><input type='text'/></div>"
console.log(str.match(reg2));//"<div class='v1'><div class='v2'>test</div>"
区间量词的非贪欲情势
平常状况下, 非贪欲情势, 我们应用的是”*?”, 或 “+?” 这类情势, 另有一种是 “{n,m}?”.
区间量词”{n,m}” 也是婚配优先, 虽有婚配次数上限, 然则在抵达上限之前, 它依然是只管多的婚配, 而”{n,m}?” 则示意在区间局限内, 只管少的婚配.
需要注重的是:
能到达一样婚配效果的贪欲与非贪欲情势, 平常是贪欲情势的婚配效力较高.
一切的非贪欲情势, 都可以经由历程修正量词润饰的子表达式, 转换为贪欲情势.
贪欲情势可以与
固化分组
(背面会讲到)连系,提拔婚配效力,而非贪欲情势却不可以.
分组
正则的分组重要经由历程小括号来完成, 括号包裹的子表达式作为一个分组, 括号后可以紧跟限定词示意重复次数. 以下, 小括号内包裹的abc就是一个分组:
/(abc)+/.test("abc123") == true
那末分组有什么用呢? 平常来说, 分组是为了轻易的示意重复次数, 除此以外, 另有一个作用就是用于捕捉, 请往下看.
捕捉性分组
捕捉性分组, 平常由一对小括号加上子表达式构成. 捕捉性分组会建立反向援用, 每一个反向援用都由一个编号或称号来标识, js中重如果经由历程 $+编号
或许 \+编号
示意法举行援用. 以下就是一个捕捉性分组的例子.
var color = "#808080";
var output = color.replace(/#(\d+)/,"$1"+"~~");//天然也可以写成 "$1~~"
console.log(RegExp.$1);//808080
console.log(output);//808080~~
以上, (d+) 示意一个捕捉性分组, “RegExp.$1” 指向该分组捕捉的内容. $+编号
这类援用平常在正则表达式以外应用. \+编号
这类援用却可以在正则表达式中应用, 可用于婚配差别位置雷同部份的子串.
var url = "www.google.google.com";
var re = /([a-z]+)\.\1/;
console.log(url.replace(re,"$1"));//"www.google.com"
以上, 雷同部份的”google”字符串只被替代一次.
非捕捉性分组
非捕捉性分组, 平常由一对括号加上”?:”加上子表达式构成, 非捕捉性分组不会建立反向援用, 就好像没有括号一样. 以下:
var color = "#808080";
var output = color.replace(/#(?:\d+)/,"$1"+"~~");
console.log(RegExp.$1);//""
console.log(output);//$1~~
以上, (?:d+) 示意一个非捕捉性分组, 由于分组不捕捉任何内容, 所以, RegExp.$1 就指向了空字符串.
同时, 由于$1 的反向援用不存在, 因而终究它被当成了平常字符串举行替代.
实际上, 捕捉性分组和无捕捉性分组在搜刮效力方面也没什么差别, 没有哪个比另一个更快.
定名分组
语法: (?<name>…)
定名分组也是捕捉性分组, 它将婚配的字符串捕捉到一个组称号或编号称号中, 在取得婚配效果后, 可经由历程分组名举行猎取. 以下是一个python的定名分组的例子.
import re
data = "#808080"
regExp = r"#(?P<one>\d+)"
replaceString = "\g<one>" + "~~"
print re.sub(regExp,replaceString,data) # 808080~~
python的定名分组表达式与范例花样比拟, 在 ? 后多了一大写的 P 字符, 而且python经由历程“g<定名>”示意法举行援用. (如果是捕捉性分组, python经由历程”g<编号>”示意法举行援用)
与python差别的是, javaScript 中并不支撑定名分组.
固化分组
固化分组, 又叫原子组.
语法: (?>…)
如上所述, 我们在应用非贪欲情势时, 婚配历程当中能够会举行屡次的回溯, 回溯越多, 正则表达式的运转效力就越低. 而固化分组就是用来削减回溯次数的.
实际上, 固化分组(?>…)的婚配与一般的婚配并没有离别, 它并不会转变婚配效果. 唯一的差别就是: 固化分组婚配完毕时, 它婚配到的文本已固化为一个单位, 只能作为团体而保留或摒弃, 括号内的子表达式中何尝试过的备用状况都邑被摒弃, 所以回溯永久也不能挑选个中的状况(因而不能介入回溯). 下面我们来经由历程一个例子更好地明白固化分组.
如果要处置惩罚一批数据, 原花样为 123.456, 由于浮点数显现题目, 部份数据花样会变成123.456000000789这类, 现请求只保留小数点后2~3位, 然则末了一名不能为0, 那末这个正则怎样写呢?
var str = "123.456000000789";
str = str.replace(/(\.\d\d[1-9]?)\d*/,"$1"); //123.456
以上的正则, 关于”123.456″ 这类花样的数据, 将白白处置惩罚一遍. 为了进步效力, 我们将正则末了的一个”*”改成”+”. 以下:
var str = "123.456";
str = str.replace(/(\.\d\d[1-9]?)\d+/,"$1"); //123.45
此时, “dd[1-9]?” 子表达式, 婚配是 “45”, 而不是 “456”, 这是由于正则末端应用了”+”, 示意末端起码要婚配一个数字, 因而末端的子表达式”d+” 婚配到了 “6”. 明显 “123.45” 不是我们希冀的婚配效果, 那我们应当怎样做呢? 可否让 “[1-9]?” 一旦婚配胜利, 便不再举行回溯, 这里就要用到我们上面说的固化分组.
“(\.dd(?>[1-9]?))d+” 就是上述正则的固化分组情势. 由于字符串 “123.456” 不满足该固化分组的正则, 所以, 婚配会失利, 相符我们希冀.
下面我们来剖析下固化分组的正则 (\.dd(?>[1-9]?))d+ 为何婚配不到字符串”123.456″.
很明显, 关于上述固化分组, 只存在两种婚配效果.
状况①: 若 [1-9] 婚配失利, 正则会返回 ? 留下的备用状况. 然后婚配脱离固化分组, 继承前进到[d+]. 当控制权脱离固化分组时, 没有备用状况需要摒弃(因固化分组中基础没有建立任何备用状况).
状况②: 若 [1-9] 婚配胜利, 婚配脱离固化分组以后, ? 保留的备用状况依然存在, 然则, 由于它属于已完毕的固化分组, 所以会被扬弃.
关于字符串 “123.456”, 由于 [1-9] 可以婚配胜利, 所以它相符状况②. 下面我们来复原状况②的实行现场.
婚配所处的状况: 婚配已走到了 “6” 的位置, 婚配将继承前进;==>
子表达式 d+ 发明没法婚配, 正则引擎便尝试回溯;==>
检察是不是存在备用状况以供回溯?==>
“?” 保留的备用状况属于已完毕的固化分组, 所以该备用状况会被摒弃;==>
此时固化分组婚配到的 “6”, 便不能用于正则引擎的回溯;==>
尝试回溯失利;==>
正则婚配失利.==>
文本 “123.456” 没有被正则表达式婚配上, 相符预期.
响应的流程图以下:
遗憾的是, javaScript, java 和 python中并不支撑固化分组的语法, 不过, 它在php和.NET中表现优越. 下面供应了一个php版的固化分组情势的正则表达式, 以供尝试.
$str = "123.456";
echo preg_replace("/(\.\d\d(?>[1-9]?))\d+/","\\1",$str); //固化分组
不仅如此, php还供应了占领量词优先的语法. 以下:
$str = "123.456";
echo preg_replace("/(\.\d\d[1-9]?+)\d+/","\\1",$str); //占领量词优先
虽然java不支撑固化分组的语法, 但java也供应了占领量词优先的语法, 一样可以防备正则回溯. 以下:
String str = "123.456";
System.out.println(str.replaceAll("(\\.\\d\\d[1-9]?+)\\d+", "$1"));// 123.456
值得注重的是: java中 replaceAll 要领需要转义反斜杠.
正则表达式高阶妙技-零宽断言
如果说正则分组是写轮眼, 那末零宽断言就是万花筒写轮眼最终奥义-须佐能乎(这里借火影忍术打个比如). 合理地应用零宽断言, 可以能分组之不能, 极大地加强正则婚配才能, 它以至可以协助你在婚配前提异常隐约的状况下疾速地定位文本.
零宽断言, 又叫环顾. 环顾只举行子表达式的婚配, 婚配到的内容不保留到终究的婚配效果, 由于婚配是零宽度的, 故终究婚配到的只是一个位置.
环顾根据方向分别, 有递次和逆序两种(也叫前瞻和后瞻), 根据是不是婚配有一定和否认两种, 组合之, 便有4种环顾. 4种环顾并不庞杂, 以下就是它们的形貌.
字符 | 形貌 | 示例 |
---|---|---|
(?:pattern) | 非捕捉性分组, 婚配pattern的位置, 但不捕捉婚配效果.也就是说不建立反向援用, 就好像没有括号一样. | ‘abcd(?:e)婚配’abcde |
(?=pattern) | 递次一定环顾, 婚配背面是pattern 的位置, 不捕捉婚配效果. | ‘Windows (?=2000)’婚配 “Windows2000” 中的 “Windows”; 不婚配 “Windows3.1” 中的 “Windows” |
(?!pattern) | 递次否认环顾, 婚配背面不是 pattern 的位置, 不捕捉婚配效果. | ‘Windows (?!2000)’婚配 “Windows3.1” 中的 “Windows”; 不婚配 “Windows2000” 中的 “Windows” |
(?<=pattern) | 逆序一定环顾, 婚配前面是 pattern 的位置, 不捕捉婚配效果. | ‘(?<=Office)2000’婚配 ” Office2000″ 中的 “2000”; 不婚配 “Windows2000” 中的 “2000” |
(?<!pattern) | 逆序否认环顾, 婚配前面不是 pattern 的位置, 不捕捉婚配效果. | ‘(?<!Office)2000’婚配 ” Windows2000″ 中的 “2000”; 不婚配 ” Office2000″ 中的 “2000” |
非捕捉性分组由于组织与环顾相似, 故列在表中, 以做对照. 以上4种环顾中, 如今 javaScript 中只支撑前两种, 也就是只支撑 递次一定环顾 和 递次否认环顾. 下面我们经由历程实例来协助明白下:
var str = "123abc789",s;
//没有应用环顾,abc直接被替代
s = str.replace(/abc/,456);
console.log(s); //123456789
//应用了递次一定环顾,捕捉到了a前面的位置,所以abc没有被替代,只是将3替代成了3456
s = str.replace(/3(?=abc)/,3456);
console.log(s); //123456abc789
//应用了递次否认环顾,由于3背面跟着abc,不满意前提,故捕捉失利,所以原字符串没有被替代
s = str.replace(/3(?!abc)/,3456);
console.log(s); //123abc789
下面经由历程python来演示下 逆序一定环顾 和 逆序否认环顾 的用法.
import re
data = "123abc789"
# 应用了逆序一定环顾,替代左边为123的一连的小写英笔墨母,婚配胜利,故abc被替代为456
regExp = r"(?<=123)[a-z]+"
replaceString = "456"
print re.sub(regExp,replaceString,data) # 123456789
# 应用了逆序否认环顾,由于英笔墨母左边不能为123,故子表达式[a-z]+捕捉到bc,终究bc被替代为456
regExp = r"(?<!123)[a-z]+"
replaceString = "456"
print re.sub(regExp,replaceString,data) # 123a456789
需要注重的是: python 和 perl 言语中的 逆序环顾 的子表达式只能应用定长的文本. 比如将上述 “(?<=123)” (逆序一定环顾)子表达式写成 “(?<=[0-9]+)”, python诠释器将会报错: “error: look-behind requires fixed-width pattern”.
场景回忆
猎取html片断
如果如今, js 经由历程 ajax 猎取到一段 html 代码以下:
var responseText = "<div data='dev.xxx.txt'></div><img src='dev.xxx.png' />";
现我们需要替代img标签的src 属性中的 “dev”字符串 为 “test” 字符串.
① 由于上述 responseText 字符串中包括起码两个子字符串 “dev”, 明显不能直接 replace 字符串 “dev”为 “test”.
② 同时由于 js 中不支撑逆序环顾, 我们也不能在正则中推断前缀为 “src='”, 然后再替代”dev”.
③ 我们注重到 img 标签的 src 属性以 “.png” 末端, 基于此, 就可以应用递次一定环顾. 以下:
var reg = /dev(?=[^']*png)/; //为了防备婚配到第一个dev, 通配符前面需要消除单引号或许是尖括号
var str = responseText.replace(reg,"test");
console.log(str);//<div data='dev.xxx'></div><img src='test.xxx.png' />
固然, 以上不止递次一定环顾一种解法, 捕捉性分组一样可以做到. 那末环顾高等在那里呢? 环顾高等的处所就在于它经由历程一次捕捉就可以定位到一个位置, 关于庞杂的文本替代场景, 常有奇效, 而分组则需要更多的操纵.
千位支解符
千位分隔符, 望文生义, 就是数字中的逗号. 参考西方的习气, 数字当中到场一个标记, 防备因数字太长难以直观的看出它的值. 故而数字当中, 每隔三位增加一个逗号, 即千位分隔符.
那末怎样将一串数字转化为千位分隔符情势呢?
var str = "1234567890";
(+str).toLocaleString();//"1,234,567,890"
如上, toLocaleString()
返回当前对象的”本地化”字符串情势.
如果该对象是Number范例, 那末将返回该数值的根据特定标记支解的字符串情势.
如果该对象是Array范例, 那末先将数组中的每项转化为字符串, 然后将这些字符串以指定分隔符连接起来并返回.
toLocaleString
要领特别, 有本地化特征, 关于天朝, 默许的分隔符是英文逗号. 因而应用它正好可以将数值转化为千位分隔符情势的字符串. 如果考虑到国际化, 以上要领就有能够会失效了.
我们尝试应用环顾来处置惩罚下.
function thousand(str){
return str.replace(/(?!^)(?=([0-9]{3})+$)/g,',');
}
console.log(thousand(str));//"1,234,567,890"
console.log(thousand("123456"));//"123,456"
console.log(thousand("1234567879876543210"));//"1,234,567,879,876,543,210"
上述应用到的正则分为两块. (?!^)
和 (?=([0-9]{3})+$)
. 我们先来看背面的部份, 然后逐渐剖析之.
“[0-9]{3}” 示意一连3位数字.
“([0-9]{3})+” 示意一连3位数字起码涌现一次或更屡次.
“([0-9]{3})+$” 示意一连3的正整数倍的数字, 直到字符串末端.
那末
(?=([0-9]{3})+$)
就示意婚配一个零宽度的位置, 而且从这个位置到字符串末端, 中心具有3的正整数倍的数字.正则表达式应用全局婚配g, 示意婚配到一个位置后, 它会继承婚配, 直至婚配不到.
将这个位置替代为逗号, 实际上就是每3位数字增加一个逗号.
固然关于字符串”123456″这类恰好具有3的正整数倍的数字的, 固然不能在1前面增加逗号. 那末应用
(?!^)
就指定了这个替代的位置不能为肇端位置.
千位分隔符实例, 展现了环顾的壮大, 一步到位.
正则表达式在JS中的应用
ES6对正则的扩大
ES6对正则扩大了又两种润饰符(其他言语能够不支撑):
y (粘连sticky润饰符), 与g相似, 也是全局婚配, 而且下一次婚配都是从上一次婚配胜利的下一个位置最先, 差别的地方在于, g润饰符只需盈余位置中存在婚配即可, 而y润饰符确保婚配必需从盈余的第一个位置最先.
var s = "abc_ab_a";
var r1 = /[a-z]+/g;
var r2 = /[a-z]+/y;
console.log(r1.exec(s),r1.lastIndex); // ["abc", index: 0, input: "abc_ab_a"] 3
console.log(r2.exec(s),r2.lastIndex); // ["abc", index: 0, input: "abc_ab_a"] 3
console.log(r1.exec(s),r1.lastIndex); // ["ab", index: 4, input: "abc_ab_a"] 6
console.log(r2.exec(s),r2.lastIndex); // null 0
如上, 由于第二次婚配的最先位置是下标3, 对应的字符串是 “_”, 而应用y润饰符的正则对象r2, 需要从盈余的第一个位置最先, 所以婚配失利, 返回null.
正则对象的 sticky 属性, 示意是不是设置了y润饰符. 这点将会在背面讲到.
u 润饰符, 供应了对正则表达式增加4字节码点的支撑. 比如 “?” 字符是一个4字节字符, 直接应用正则婚配将会失利, 而应用u润饰符后, 将会比及准确的效果.
var s = "?";
console.log(/^.$/.test(s));//false
console.log(/^.$/u.test(s));//true
UCS-2字节码
有关字节码点, 轻微提下. javaScript 只能处置惩罚UCS-2编码(js于1995年5月被Brendan Eich消费10天设想出来, 比1996年7月宣布的编码范例UTF-16早了一年多, 当时只要UCS-2可选). 由于UCS-2先天不足, 造成了一切字符在js中都是2个字节. 如果是4个字节的字符, 将会默许被看成两个双字节字符处置惩罚. 因而 js 的字符处置惩罚函数都邑受到限制, 没法返回准确效果. 以下:
var s = "?";
console.log(s == "\uD834\uDF06");//true ?相当于UTF-16中的0xD834DF06
console.log(s.length);//2 长度为2, 示意这是4字节字符
荣幸的是, ES6可以自动辨认4字节的字符.因而遍历字符串可以直接应用for of轮回. 同时, js中如果直接应用码点示意Unicode字符, 关于4字节字符, ES5里是没办法辨认的. 为此ES6修复了这个题目, 只需将码点放在大括号内即可.
console.log(s === "\u1D306");//false ES5没法辨认?
console.log(s === "\u{1D306}");//true ES6可以借助大括号辨认?
附: ES6新增的处置惩罚4字节码的函数
String.fromCodePoint():从Unicode码点返回对应字符
String.prototype.codePointAt():从字符返回对应的码点
String.prototype.at():返回字符串给定位置的字符
有关js中的unicode字符集, 请参考阮一峰先生的 Unicode与JavaScript详解
.
以上是ES6对正则的扩大. 另一个方面, 从要领上看, javaScript 中与正则表达式有关的要领有:
要领名 | compile | test | exec | match | search | replace | split |
---|---|---|---|---|---|---|---|
所属对象 | RegExp | RegExp | RegExp | String | String | String | String |
由上, 一共有7个与js相干的要领, 这些要领离别来自于 RegExp 与 String 对象. 起首我们先来看看js中的正则类 RegExp.
RegExp
RegExp 对象示意正则表达式, 重要用于对字符串实行情势婚配.
语法: new RegExp(pattern[, flags])
参数 pattern 是一个字符串, 指定了正则表达式字符串或其他的正则表达式对象.
参数 flags 是一个可选的字符串, 包括属性 “g”、”i” 和 “m”, 离别用于指定全局婚配、辨别大小写的婚配和多行婚配. 如果pattern 是正则表达式, 而不是字符串, 则必需省略该参数.
var pattern = "[0-9]";
var reg = new RegExp(pattern,"g");
// 上述建立正则表达式对象,可以用对象字面量情势替代,也引荐下面这类
var reg = /[0-9]/g;
以上, 经由历程对象字面量和组织函数建立正则表达式, 有个小插曲.
“关于正则表达式的直接量, ECMAscript 3划定在每次它时都邑返回同一个RegExp对象, 因而用直接量建立的正则表达式的会同享一个实例. 直到ECMAScript 5才划定每次返回差别的实例.”
所以, 如今我们基础不必忧郁这个题目, 只需要注重在低版本的非IE浏览器中只管应用组织函数建立正则(这点上, IE一向恪守ES5划定, 其他浏览器的初级版本遵照ES3划定).
RegExp 实例对象包括以下属性:
实例属性 | 形貌 |
---|---|
global | 是不是包括全局标志(true/false) |
ignoreCase | 是不是包括辨别大小写标志(true/false) |
multiline | 是不是包括多行标志(true/false) |
source | 返回建立RegExp对象实例时指定的表达式文本字符串情势 |
lastIndex | 示意原字符串中婚配的字符串末端的后一个位置, 默许为0 |
flags(ES6) | 返回正则表达式的润饰符 |
sticky(ES6) | 是不是设置了y(粘连)润饰符(true/false) |
compile
compile 要领用于在实行历程当中转变和从新编译正则表达式.
语法: compile(pattern[, flags])
参数引见请参考上述 RegExp 组织器. 用法以下:
var reg = new RegExp("abc", "gi");
var reg2 = reg.compile("new abc", "g");
console.log(reg);// /new abc/g
console.log(reg2);// undefined
可见 compile 要领会转变原正则表达式对象, 并从新编译, 而且它的返回值为空.
test
test 要领用于检测一个字符串是不是婚配某个正则划定规矩, 只需是字符串中含有与正则划定规矩婚配的文本, 该要领就返回true, 不然返回 false.
语法: test(string), 用法以下:
console.log(/[0-9]+/.test("abc123"));//true
console.log(/[0-9]+/.test("abc"));//false
以上, 字符串”abc123″ 包括数字, 故 test 要领返回 true; 而 字符串”abc” 不包括数字, 故返回 false.
如果需要应用 test 要领测试字符串是不是完成婚配某个正则划定规矩, 那末可以在正则表达式里增添最先(^)和完毕($)元字符. 以下:
console.log(/^[0-9]+$/.test("abc123"));//false
以上, 由于字符串”abc123″ 并不是以数字最先, 也并不是以数字完毕, 故 test 要领返回false.
实际上, 如果正则表达式带有全局标志(带有参数g)时, test 要领还受正则对象的lastIndex属性影响,以下:
var reg = /[a-z]+/;//正则不带全局标志
console.log(reg.test("abc"));//true
console.log(reg.test("de"));//true
var reg = /[a-z]+/g;//正则带有全局标志g
console.log(reg.test("abc"));//true
console.log(reg.lastIndex);//3, 下次运转test时,将从索引为3的位置最先查找
console.log(reg.test("de"));//false
该影响将在exec 要领解说中予以剖析.
exec
exec 要领用于检测字符串对正则表达式的婚配, 如果找到了婚配的文本, 则返回一个效果数组, 不然返回null.
语法: exec(string)
exec 要领返回的数组中包括两个分外的属性, index 和 input. 而且该数组具有以下特征:
第 0 个项示意正则表达式捕捉的文本
第 1~n 项示意第 1~n 个反向援用, 顺次指向第 1~n 个分组捕捉的文本, 可以应用RegExp.$ + “编号1~n” 顺次猎取分组中的文本
index 示意婚配字符串的初始位置
input 示意正在检索的字符串
不管正则表达式有没有全局标示”g”, exec 的表现都雷同. 但正则表达式对象的表现却有些差别. 下面我们来细致申明下正则表达式对象的表现都有哪些差别.
假定正则表达式对象为 reg , 检测的字符为 string , reg.exec(string) 返回值为 array.
若 reg 包括全局标示”g”, 那末 reg.lastIndex 属性示意原字符串中婚配的字符串末端的后一个位置, 即下次婚配最先的位置, 此时 reg.lastIndex == array.index(婚配最先的位置) + array[0].length(婚配字符串的长度). 以下:
var reg = /([a-z]+)/gi,
string = "World Internet Conference";
var array = reg.exec(string);
console.log(array);//["World", "World", index: 0, input: "World Internet Conference"]
console.log(RegExp.$1);//World
console.log(reg.lastIndex);//5, 恰好即是 array.index + array[0].length
跟着检索继承, array.index 的值将今后递增, 也就是说, reg.lastIndex 的值也会同步今后递增. 因而, 我们也可以经由历程重复挪用 exec 要领来遍历字符串中一切的婚配文本. 直到 exec 要领再也婚配不到文本时, 它将返回 null, 并把 reg.lastIndex 属性重置为 0.
接着上述例子, 我们继承实行代码, 看看上面说的对不对, 以下所示:
array = reg.exec(string);
console.log(array);//["Internet", "Internet", index: 6, input: "World Internet Conference"]
console.log(reg.lastIndex);//14
array = reg.exec(string);
console.log(array);//["Conference", "Conference", index: 15, input: "World Internet Conference"]
console.log(reg.lastIndex);//25
array = reg.exec(string);
console.log(array);//null
console.log(reg.lastIndex);//0
以上代码中, 跟着重复挪用 exec 要领, reg.lastIndex 属性终究被重置为 0.
题目回忆
在 test 要领的解说中, 我们留下了一个题目. 如果正则表达式带有全局标志g, 以上 test 要领的实行效果将受 reg.lastIndex影响, 不仅如此, exec 要领也一样. 由于 reg.lastIndex 的值并不老是为零, 而且它决议了下次婚配最先的位置, 如果在一个字符串中完成了一次婚配以后要最先检索新的字符串, 那就必需要手动地把 lastIndex 属性重置为 0. 防备涌现下面这类毛病:
var reg = /[0-9]+/g,
str1 = "123abc",
str2 = "123456";
reg.exec(str1);
console.log(reg.lastIndex);//3
var array = reg.exec(str2);
console.log(array);//["456", index: 3, input: "123456"]
以上代码, 准确实行效果应当是 “123456”, 因而发起在第二次实行 exec 要领前, 增添一句 “reg.lastIndex = 0;”.
若 reg 不包括全局标示”g”, 那末 exec 要领的实行效果(array)将与 string.match(reg) 要领实行效果完全雷同.
String
match, search, replace, split 要领请参考 字符串常常使用要领
中的解说.
以下展现了应用捕捉性分组处置惩罚文本模板, 终究天生完全字符串的历程:
var tmp = "An ${a} a ${b} keeps the ${c} away";
var obj = {
a:"apple",
b:"day",
c:"doctor"
};
function tmpl(t,o){
return t.replace(/\${(.)}/g,function(m,p){
console.log('m:'+m+' p:'+p);
return o[p];
});
}
tmpl(tmp,obj);
上述功用应用ES6可这么完成:
var obj = {
a:"apple",
b:"day",
c:"doctor"
};
with(obj){
console.log(`An ${a} a ${b} keeps the ${c} away`);
}
正则表达式在H5中的应用
H5中新增了 pattern 属性, 划定了用于考证输入字段的情势, pattern的情势婚配支撑正则表达式的誊写体式格局. 默许 pattern 属性是悉数婚配, 即不管正则表达式中有没有 “^”, “$” 元字符, 它都是婚配一切文本.
注: pattern 适用于以下 input 范例:text, search, url, telephone, email 以及 password. 如果需要作废表单考证, 在form标签上增添 novalidate 属性即可.
正则引擎
如今正则引擎有两种, DFA 和 NFA, NFA又可以分为传统型NFA和POSIX NFA.
DFA Deterministic finite automaton 一定型有穷自动机
NFA Non-deterministic finite automaton 非一定型有穷自动机
Traditional NFA
POSIX NFA
DFA引擎不支撑回溯, 婚配疾速, 而且不支撑捕捉组, 因而也就不支撑反向援用. 上述awk, egrep敕令均支撑 DFA引擎.
POSIX NFA重要指相符POSIX范例的NFA引擎, 像 javaScript, java, php, python, c#等言语均完成了NFA引擎.
有关正则表达式细致的婚配道理, 临时没在网上看到合适的文章, 发起选读 Jeffrey Friedl 的 <通晓正则表达式>[第三版] 中第4章-表达式的婚配道理(p143-p183), Jeffrey Friedl 对正则表达式有着深入的明白, 置信他可以协助您更好的进修正则.
有关NFA引擎的简朴完成, 可以参考文章 基于ε-NFA的正则表达式引擎 – twoon.
总结
在进修正则的初级阶段, 重在明白 ①贪欲与非贪欲情势, ②分组, ③捕捉性与非捕捉性分组, ④定名分组, ⑤固化分组, 体味设想的精巧的地方. 而高等阶段, 重要在于闇练应用⑥零宽断言(或环顾)处理题目, 而且熟习正则婚配的道理.
实际上, 正则在 javaScript 中的功用不算壮大, js 仅仅支撑了①贪欲与非贪欲情势, ②分组, ③捕捉性与非捕捉性分组 以及 ⑥零宽断言中的递次环顾. 如果再轻微熟习些 js 中7种与正则有关的要领(compile, test, exec, match, search, replace, split), 那末处置惩罚文本或字符串将游刃有余.
正则表达式, 在文本处置惩罚方面禀赋异禀, 它的功用非常壮大, 许多时刻以至是唯一处理计划. 正则不局限于js, 当下热点的编辑器(比如Sublime, Atom) 以及 IDE(比如WebStorm, IntelliJ IDEA) 都支撑它. 您以至可以在任何时刻任何言语中, 尝试应用正则处理题目, 或许之前不能处理的题目, 如今可以轻松的处理.
附其他言语正则材料:
本文作者: louis
本文简介: 本文断断续续用时两个月而成, 总计12k字, 为求简约周全地复原前端场景中正则的应用规律, 搜集了大批正则相干材料, 并剔除不少冗余字句, 码字不容易, 喜好的请点个赞?或许珍藏, 我将延续坚持更新.
原文地点: http://louiszhai.github.io/20…
参考文章
Jeffrey Friedl 的 <通晓正则表达式>[第三版]
JAVASCRIPT 正则表达式进修–>基础与零宽断言(转自司徒正美) – 随风之羽 – 博客频道 – CSDN.NET