摘要:非貪婪模式盡可能少的匹配所搜索的字符串,而默認(rèn)的貪婪模式則盡可能多的匹配所搜索的字符串。
導(dǎo)讀
你有沒有在搜索文本的時(shí)候絞盡腦汁, 試了一個(gè)又一個(gè)表達(dá)式, 還是不行.
你有沒有在表單驗(yàn)證的時(shí)候, 只是做做樣子(只要不為空就好), 然后燒香拜佛, 虔誠(chéng)祈禱, 千萬不要出錯(cuò).
你有沒有在使用sed 和 grep 命令的時(shí)候, 感覺莫名其妙, 明明應(yīng)該支持的元字符, 卻就是匹配不到.
甚至, 你壓根沒遇到過上述情況, 你只是一遍又一遍的調(diào)用 replace 而已 (把非搜索文本全部替換為空, 然后就只剩搜索文本了), 面對(duì)別人家的簡(jiǎn)潔高效的語句, 你只能在心中吶喊, replace 大法好.
為什么要學(xué)正則表達(dá)式. 有位網(wǎng)友這么說: 江湖傳說里, 程序員的正則表達(dá)式和醫(yī)生的處方, 道士的鬼符齊名, 曰: 普通人看不懂的三件神器. 這個(gè)傳說至少向我們透露了兩點(diǎn)信息: 一是正則表達(dá)式很牛, 能和醫(yī)生的處方, 道士的鬼符齊名, 并被大家提起, 可見其江湖地位. 二是正則表達(dá)式很難, 這也從側(cè)面說明了, 如果你可以熟練的掌握并應(yīng)用它, 在裝逼的路上, 你將如日中天 (別問我中天是誰……) !
顯然, 有關(guān)正則表達(dá)的介紹, 無須我多言. 這里就借助 Jeffrey Friedl 的《精通正則表達(dá)式》一書的序言正式拋個(gè)磚.
? "如果羅列計(jì)算機(jī)軟件領(lǐng)域的偉大發(fā)明, 我相信絕對(duì)不會(huì)超過二十項(xiàng), 在這個(gè)名單當(dāng)中, 當(dāng)然應(yīng)該包括分組交換網(wǎng)絡(luò), Web, Lisp, 哈希算法, UNIX, 編譯技術(shù), 關(guān)系模型, 面向?qū)ο? XML這些大名鼎鼎的家伙, 而正則表達(dá)式也絕對(duì)不應(yīng)該被漏掉.
? 對(duì)很多實(shí)際工作而言, 正則表達(dá)式簡(jiǎn)直是靈丹妙藥, 能夠成百倍的提高開發(fā)效率和程序質(zhì)量, 正則表達(dá)式在生物信息學(xué)和人類基因圖譜的研究中所發(fā)揮的關(guān)鍵作用, 更是被傳為佳話. CSDN的創(chuàng)始人蔣濤先生在早年開發(fā)專業(yè)軟件產(chǎn)品時(shí), 就曾經(jīng)體驗(yàn)過這一工具的巨大威力, 并且一直印象深刻."
因此, 我們沒有理由不去了解正則表達(dá)式, 甚至是熟練掌握并運(yùn)用它.
本文以正則基礎(chǔ)語法開篇, 結(jié)合具體實(shí)例, 逐步講解正則表達(dá)式匹配原理. 代碼實(shí)例使用語言包括 js, php, python, java(因有些匹配模式, js并未支持, 需要借助其他語言講解). 內(nèi)容包括初階技能和高階技能, 適合新手學(xué)習(xí)和進(jìn)階. 本文力求簡(jiǎn)單通俗易懂, 同時(shí)為求全面, 涉及知識(shí)較多, 共計(jì)12k字, 篇幅較長(zhǎng), 請(qǐng)耐心閱讀, 如有閱讀障礙請(qǐng)及時(shí)聯(lián)系我.
回顧歷史要論正則表達(dá)式的淵源, 最早可以追溯至對(duì)人類神經(jīng)系統(tǒng)如何工作的早期研究. Warren McCulloch 和 Walter Pitts 這兩位神經(jīng)大咖 (神經(jīng)生理學(xué)家) 研究出一種數(shù)學(xué)方式來描述這些神經(jīng)網(wǎng)絡(luò).
1956 年, 一位叫 Stephen Kleene 的數(shù)學(xué)家在 McCulloch 和 Pitts 早期工作的基礎(chǔ)上, 發(fā)表了一篇標(biāo)題為"神經(jīng)網(wǎng)事件的表示法"的論文, 引入了正則表達(dá)式的概念.
隨后, 發(fā)現(xiàn)可以將這一工作應(yīng)用于使用 Ken Thompson 的計(jì)算搜索算法的一些早期研究中. 而 Ken Thompson 又是 Unix 的主要發(fā)明人. 因此半個(gè)世紀(jì)以前的Unix 中的 qed 編輯器(1966 qed編輯器問世) 成了第一個(gè)使用正則表達(dá)式的應(yīng)用程序.
至此之后, 正則表達(dá)式成為家喻戶曉的文本處理工具, 幾乎各大編程語言都以支持正則表達(dá)式作為賣點(diǎn), 當(dāng)然 JavaScript 也不例外.
正則表達(dá)式的定義正則表達(dá)式是由普通字符和特殊字符(也叫元字符或限定符)組成的文字模板. 如下便是簡(jiǎn)單的匹配連續(xù)數(shù)字的正則表達(dá)式:
/[0-9]+/ /d+/
"d" 就是元字符, 而 "+" 則是限定符.
元字符元字符 | 描述 |
---|---|
. | 匹配除換行符以外的任意字符 |
d | 匹配數(shù)字, 等價(jià)于字符組[0-9] |
w | 匹配字母, 數(shù)字, 下劃線或漢字 |
s | 匹配任意的空白符(包括制表符,空格,換行等) |
匹配單詞開始或結(jié)束的位置 | |
^ | 匹配行首 |
$ | 匹配行尾 |
元字符 | 描述 |
---|---|
D | 匹配非數(shù)字的任意字符, 等價(jià)于[^0-9] |
W | 匹配除字母,數(shù)字,下劃線或漢字之外的任意字符 |
S | 匹配非空白的任意字符 |
B | 匹配非單詞開始或結(jié)束的位置 |
[^x] | 匹配除x以外的任意字符 |
可以看出正則表達(dá)式嚴(yán)格區(qū)分大小寫.
重復(fù)限定符限定符共有6個(gè), 假設(shè)重復(fù)次數(shù)為x次, 那么將有如下規(guī)則:
限定符 | 描述 |
---|---|
* | x>=0 |
+ | x>=1 |
? | x=0 or x=1 |
{n} | x=n |
{n,} | x>=n |
{n,m} | n<=x<=m |
[...] 匹配中括號(hào)內(nèi)字符之一. 如: [xyz] 匹配字符 x, y 或 z. 如果中括號(hào)中包含元字符, 則元字符降級(jí)為普通字符, 不再具有元字符的功能, 如 [+.?] 匹配 加號(hào), 點(diǎn)號(hào)或問號(hào).
排除性字符組[^…] 匹配任何未列出的字符,. 如: [^x] 匹配除x以外的任意字符.
多選結(jié)構(gòu)| 就是或的意思, 表示兩者中的一個(gè). 如: a|b 匹配a或者b字符.
括號(hào)括號(hào) 常用來界定重復(fù)限定符的范圍, 以及將字符分組. 如: (ab)+ 可以匹配abab..等, 其中 ab 便是一個(gè)分組.
轉(zhuǎn)義字符即轉(zhuǎn)義字符, 通常 * + ? | { [ ( ) ] }^ $ . # 和 空白 這些字符都需要轉(zhuǎn)義.
操作符的運(yùn)算優(yōu)先級(jí)轉(zhuǎn)義符
(), (?:), (?=), [] 圓括號(hào)或方括號(hào)
*, +, ?, {n}, {n,}, {n,m} 限定符
^, $ 位置
| "或" 操作
測(cè)試我們來測(cè)試下上面的知識(shí)點(diǎn), 寫一個(gè)匹配手機(jī)號(hào)碼的正則表達(dá)式, 如下:
(+86)?1d{10}
① "+86" 匹配文本 "+86", 后面接元字符問號(hào), 表示可匹配1次或0次, 合起來表示 "(+86)?" 匹配 "+86" 或者 "".
② 普通字符"1" 匹配文本 "1".
③ 元字符 "d" 匹配數(shù)字0到9, 區(qū)間量詞 "{10}" 表示匹配 10 次, 合起來表示 "d{10}" 匹配連續(xù)的10個(gè)數(shù)字.
以上, 匹配結(jié)果如下:
修飾符javaScript中正則表達(dá)式默認(rèn)有如下五種修飾符:
g (全文查找), 如上述截圖, 實(shí)際上就開啟了全文查找模式.
i (忽略大小寫查找)
m (多行查找)
y (ES6新增的粘連修飾符)
u (ES6新增)
常用的正則表達(dá)式漢字: ^[u4e00-u9fa5]{0,}$
Email: ^w+([-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*$
URL: ^https?://([w-]+.)+[w-]+(/[w-./?%&=]*)?$
手機(jī)號(hào)碼: ^1d{10}$
身份證號(hào): ^(d{15}|d{17}(d|X))$
中國(guó)郵政編碼: [1-9]d{5}(?!d) (郵政編碼為6位數(shù)字)
密碼驗(yàn)證密碼驗(yàn)證是常見的需求, 一般來說, 常規(guī)密碼大致會(huì)滿足規(guī)律: 6-16位, 數(shù)字, 字母, 字符至少包含兩種, 同時(shí)不能包含中文和空格. 如下便是常規(guī)密碼驗(yàn)證的正則描述:
var reg = /(?!^[0-9]+$)(?!^[A-z]+$)(?!^[^A-z0-9]+$)^[^su4e00-u9fa5]{6,16}$/;正則的幾大家族 正則表達(dá)式分類
在 linux 和 osx 下, 常見的正則表達(dá)式, 至少有以下三種:
基本的正則表達(dá)式( Basic Regular Expression 又叫 Basic RegEx 簡(jiǎn)稱 BREs )
擴(kuò)展的正則表達(dá)式( Extended Regular Expression 又叫 Extended RegEx 簡(jiǎn)稱 EREs )
Perl 的正則表達(dá)式( Perl Regular Expression 又叫 Perl RegEx 簡(jiǎn)稱 PREs )
正則表達(dá)式比較字符 | 說明 | Basic RegEx | Extended RegEx | python RegEx | Perl regEx |
---|---|---|---|---|---|
轉(zhuǎn)義 | |||||
^ | 匹配行首,例如"^dog"匹配以字符串dog開頭的行(注意:awk 指令中,"^"則是匹配字符串的開始) | ^ | ^ | ^ | ^ |
$ | 匹配行尾,例如:"^、dog$" 匹配以字符串 dog 為結(jié)尾的行(注意:awk 指令中,"$"則是匹配字符串的結(jié)尾) | $ | $ | $ | $ |
^$ | 匹配空行 | ^$ | ^$ | ^$ | ^$ |
^string$ | 匹配行,例如:"^dog$"匹配只含一個(gè)字符串 dog 的行 | ^string$ | ^string$ | ^string$ | ^string$ |
< | 匹配單詞,例如:"< |
< |
不支持 |
不支持(但可以使用b來匹配單詞,例如:"bfrog") |
|
> | 匹配單詞,例如:"frog>"(等價(jià)于"frogb "),匹配以 frog 結(jié)尾的單詞 | > | > | 不支持 | 不支持(但可以使用b來匹配單詞,例如:"frogb") |
匹配一個(gè)單詞或者一個(gè)特定字符,例如:" |
不支持 | 不支持(但可以使用b來匹配單詞,例如:"bfrogb" | |||
() | 匹配表達(dá)式,例如:不支持"(frog)" | 不支持(但可以使用,如:dog | () | () | () |
匹配表達(dá)式,例如:不支持"(frog)" | 不支持(同()) | 不支持(同()) | 不支持(同()) | ||
? | 匹配前面的子表達(dá)式 0 次或 1 次(等價(jià)于{0,1}),例如:where(is)?能匹配"where" 以及"whereis" | 不支持(同?) | ? | ? | ? |
? | 匹配前面的子表達(dá)式 0 次或 1 次(等價(jià)于"{0,1}"),例如:"whereis? "能匹配 "where"以及"whereis" | ? | 不支持(同?) | 不支持(同?) | 不支持(同?) |
? | 當(dāng)該字符緊跟在任何一個(gè)其他限制符(*, +, ?, {n},{n,}, {n,m}) 后面時(shí),匹配模式是非貪婪的。非貪婪模式盡可能少的匹配所搜索的字符串,而默認(rèn)的貪婪模式則盡可能多的匹配所搜索的字符串。例如,對(duì)于字符串 "oooo","o+?" 將匹配單個(gè)"o",而 "o+" 將匹配所有 "o" | 不支持 | 不支持 | 不支持 | 不支持 |
. | 匹配除換行符("n")之外的任意單個(gè)字符(注意:awk 指令中的句點(diǎn)能匹配換行符) | . | .(如果要匹配包括“n”在內(nèi)的任何一個(gè)字符,請(qǐng)使用: [sS] | . | .(如果要匹配包括“n”在內(nèi)的任何一個(gè)字符,請(qǐng)使用:" [.n] " |
* | 匹配前面的子表達(dá)式 0 次或多次(等價(jià)于{0, }),例如:zo* 能匹配 "z"以及 "zoo" | * | * | * | * |
+ | 匹配前面的子表達(dá)式 1 次或多次(等價(jià)于"{1, }"),例如:"whereis+ "能匹配 "whereis"以及"whereisis" | + | 不支持(同+) | 不支持(同+) | 不支持(同+) |
+ | 匹配前面的子表達(dá)式 1 次或多次(等價(jià)于{1, }),例如:zo+能匹配 "zo"以及 "zoo",但不能匹配 "z" | 不支持(同+) | + | + | + |
{n} | n 必須是一個(gè) 0 或者正整數(shù),匹配子表達(dá)式 n 次,例如:zo{2}能匹配 | 不支持(同{n}) | {n} | {n} | {n} |
{n,} | "zooz",但不能匹配 "Bob"n 必須是一個(gè) 0 或者正整數(shù),匹配子表達(dá)式大于等于 n次,例如:go{2,} | 不支持(同{n,}) | {n,} | {n,} | {n,} |
{n,m} | 能匹配 "good",但不能匹配 godm 和 n 均為非負(fù)整數(shù),其中 n <= m,最少匹配 n 次且最多匹配 m 次 ,例如:o{1,3}將配"fooooood" 中的前三個(gè) o(請(qǐng)注意在逗號(hào)和兩個(gè)數(shù)之間不能有空格) | 不支持(同{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 中的任意一個(gè)數(shù)字字符(注意:要寫成遞增) | [0-9] | [0-9] | [0-9] | [0-9] |
[xyz] | 字符集合,匹配所包含的任意一個(gè)字符,例如:"[abc]"可以匹配"lay" 中的 "a"(注意:如果元字符,例如:. *等,它們被放在[ ]中,那么它們將變成一個(gè)普通字符) | [xyz] | [xyz] | [xyz] | [xyz] |
[^xyz] | 負(fù)值字符集合,匹配未包含的任意一個(gè)字符(注意:不包括換行符),例如:"[^abc]" 可以匹配 "Lay" 中的"L"(注意:[^xyz]在awk 指令中則是匹配未包含的任意一個(gè)字符+換行符) | [^xyz] | [^xyz] | [^xyz] | [^xyz] |
[A-Za-z] | 匹配大寫字母或者小寫字母中的任意一個(gè)字符(注意:要寫成遞增) | [A-Za-z] | [A-Za-z] | [A-Za-z] | [A-Za-z] |
[^A-Za-z] | 匹配除了大寫與小寫字母之外的任意一個(gè)字符(注意:寫成遞增) | [^A-Za-z] | [^A-Za-z] | [^A-Za-z] | [^A-Za-z] |
d | 匹配從 0 到 9 中的任意一個(gè)數(shù)字字符(等價(jià)于 [0-9]) | 不支持 | 不支持 | d | d |
D | 匹配非數(shù)字字符(等價(jià)于 1) | 不支持 | 不支持 | D | D |
S | 匹配任何非空白字符(等價(jià)于2) | 不支持 | 不支持 | S | S |
s | 匹配任何空白字符,包括空格、制表符、換頁(yè)符等等(等價(jià)于[ fnrtv]) | 不支持 | 不支持 | s | s |
W | 匹配任何非單詞字符 (等價(jià)于3) | W | W | W | W |
w | 匹配包括下劃線的任何單詞字符(等價(jià)于[A-Za-z0-9_]) | w | w | w | w |
B | 匹配非單詞邊界,例如:"erB" 能匹配 "verb" 中的"er",但不能匹配"never" 中的"er" | B | B | B | B |
匹配一個(gè)單詞邊界,也就是指單詞和空格間的位置,例如: "erb" 可以匹配"never" 中的 "er",但不能匹配 "verb" 中的"er" | |||||
匹配一個(gè)橫向制表符(等價(jià)于 x09和 cI) | 不支持 | 不支持 | |||
v | 匹配一個(gè)垂直制表符(等價(jià)于 x0b和 cK) | 不支持 | 不支持 | v | v |
匹配一個(gè)換行符(等價(jià)于 x0a 和cJ) | 不支持 | 不支持 | |||
f | 匹配一個(gè)換頁(yè)符(等價(jià)于x0c 和cL) | 不支持 | 不支持 | f | f |
匹配一個(gè)回車符(等價(jià)于 x0d 和cM) | 不支持 | 不支持 | |||
匹配轉(zhuǎn)義字符本身"" | |||||
cx | 匹配由 x 指明的控制字符,例如:cM匹配一個(gè)Control-M 或回車符,x 的值必須為A-Z 或 a-z 之一,否則,將 c 視為一個(gè)原義的 "c" 字符 | 不支持 | 不支持 | cx | |
xn | 匹配 n,其中 n 為十六進(jìn)制轉(zhuǎn)義值。十六進(jìn)制轉(zhuǎn)義值必須為確定的兩個(gè)數(shù)字長(zhǎng),例如:"x41" 匹配 "A"。"x041" 則等價(jià)于"x04" & "1"。正則表達(dá)式中可以使用 ASCII 編碼 | 不支持 | 不支持 | xn | |
num | 匹配 num,其中 num是一個(gè)正整數(shù)。表示對(duì)所獲取的匹配的引用 | 不支持 | num | num | |
[:alnum:] | 匹配任何一個(gè)字母或數(shù)字([A-Za-z0-9]),例如:"[[:alnum:]] " | [:alnum:] | [:alnum:] | [:alnum:] | [:alnum:] |
[:alpha:] | 匹配任何一個(gè)字母([A-Za-z]), 例如:" [[:alpha:]] " | [:alpha:] | [:alpha:] | [:alpha:] | [:alpha:] |
[:digit:] | 匹配任何一個(gè)數(shù)字([0-9]),例如:"[[:digit:]] " | [:digit:] | [:digit:] | [:digit:] | [:digit:] |
[:lower:] | 匹配任何一個(gè)小寫字母([a-z]), 例如:" [[:lower:]] " | [:lower:] | [:lower:] | [:lower:] | [:lower:] |
[:upper:] | 匹配任何一個(gè)大寫字母([A-Z]) | [:upper:] | [:upper:] | [:upper:] | [:upper:] |
[:space:] | 任何一個(gè)空白字符: 支持制表符、空格,例如:" [[:space:]] " | [:space:] | [:space:] | [:space:] | [:space:] |
[:blank:] | 空格和制表符(橫向和縱向),例如:"[[:blank:]]"ó"[stv]" | [:blank:] | [:blank:] | [:blank:] | [:blank:] |
[:graph:] | 任何一個(gè)可以看得見的且可以打印的字符(注意:不包括空格和換行符等),例如:"[[:graph:]] " | [:graph:] | [:graph:] | [:graph:] | [:graph:] |
[:print:] | 任何一個(gè)可以打印的字符(注意:不包括:[:cntrl:]、字符串結(jié)束符"0"、EOF 文件結(jié)束符(-1), 但包括空格符號(hào)),例如:"[[:print:]] " | [:print:] | [:print:] | [:print:] | [:print:] |
[:cntrl:] | 任何一個(gè)控制字符(ASCII 字符集中的前 32 個(gè)字符,即:用十進(jìn)制表示為從 0 到31,例如:換行符、制表符等等),例如:" [[:cntrl:]]" | [:cntrl:] | [:cntrl:] | [:cntrl:] | [:cntrl:] |
[:punct:] | 任何一個(gè)標(biāo)點(diǎn)符號(hào)(不包括:[:alnum:]、[:cntrl:]、[:space:]這些字符集) | [:punct:] | [:punct:] | [:punct:] | [:punct:] |
[:xdigit:] | 任何一個(gè)十六進(jìn)制數(shù)(即:0-9,a-f,A-F) | [:xdigit:] | [:xdigit:] | [:xdigit:] | [:xdigit:] |
注意
js中支持的是EREs.
當(dāng)使用 BREs ( 基本正則表達(dá)式 ) 時(shí),必須在下列這些符號(hào)(?,+,|,{,},(,))前加上轉(zhuǎn)義字符 .
上述[[:xxxx:]] 形式的正則表達(dá)式, 是php中內(nèi)置的通用字符簇, js中并不支持.
linux/osx下常用命令與正則表達(dá)式的關(guān)系我曾經(jīng)嘗試在 grep 和 sed 命令中書寫正則表達(dá)式, 經(jīng)常發(fā)現(xiàn)不能使用元字符, 而且有時(shí)候需要轉(zhuǎn)義, 有時(shí)候不需要轉(zhuǎn)義, 始終不能摸清它的規(guī)律. 如果恰好你也有同樣的困惑, 那么請(qǐng)往下看, 相信應(yīng)該能有所收獲.
grep , egrep , sed , awk 正則表達(dá)式特點(diǎn)grep 支持:BREs、EREs、PREs 正則表達(dá)式
grep 指令后不跟任何參數(shù), 則表示要使用 "BREs"
grep 指令后跟 ”-E" 參數(shù), 則表示要使用 "EREs"
grep 指令后跟 “-P" 參數(shù), 則表示要使用 "PREs"
egrep 支持:EREs、PREs 正則表達(dá)式
egrep 指令后不跟任何參數(shù), 則表示要使用 "EREs"
egrep 指令后跟 “-P" 參數(shù), 則表示要使用 "PREs"
sed 支持: BREs、EREs
sed 指令默認(rèn)是使用 "BREs"
sed 指令后跟 "-r" 參數(shù) , 則表示要使用“EREs"
awk 支持 EREs, 并且默認(rèn)使用 "EREs"
正則表達(dá)式初階技能 貪婪模式與非貪婪模式默認(rèn)情況下, 所有的限定詞都是貪婪模式, 表示盡可能多的去捕獲字符; 而在限定詞后增加?, 則是非貪婪模式, 表示盡可能少的去捕獲字符. 如下:
var str = "aaab", reg1 = /a+/, //貪婪模式 reg2 = /a+?/;//非貪婪模式 console.log(str.match(reg1)); //["aaa"], 由于是貪婪模式, 捕獲了所有的a console.log(str.match(reg2)); //["a"], 由于是非貪婪模式, 只捕獲到第一個(gè)a
實(shí)際上, 非貪婪模式非常有效, 特別是當(dāng)匹配html標(biāo)簽時(shí). 比如匹配一個(gè)配對(duì)出現(xiàn)的div, 方案一可能會(huì)匹配到很多的div標(biāo)簽對(duì), 而方案二則只會(huì)匹配一個(gè)div標(biāo)簽對(duì).
var str = ""; var reg1 = /test/; //方案一,貪婪匹配 var reg2 = / /;//方案二,非貪婪匹配 console.log(str.match(reg1));//" " console.log(str.match(reg2));//"testtest"區(qū)間量詞的非貪婪模式
一般情況下, 非貪婪模式, 我們使用的是"*?", 或 "+?" 這種形式, 還有一種是 "{n,m}?".
區(qū)間量詞"{n,m}" 也是匹配優(yōu)先, 雖有匹配次數(shù)上限, 但是在到達(dá)上限之前, 它依然是盡可能多的匹配, 而"{n,m}?" 則表示在區(qū)間范圍內(nèi), 盡可能少的匹配.
需要注意的是:
能達(dá)到同樣匹配結(jié)果的貪婪與非貪婪模式, 通常是貪婪模式的匹配效率較高.
所有的非貪婪模式, 都可以通過修改量詞修飾的子表達(dá)式, 轉(zhuǎn)換為貪婪模式.
貪婪模式可以與固化分組(后面會(huì)講到)結(jié)合,提升匹配效率,而非貪婪模式卻不可以.
分組正則的分組主要通過小括號(hào)來實(shí)現(xiàn), 括號(hào)包裹的子表達(dá)式作為一個(gè)分組, 括號(hào)后可以緊跟限定詞表示重復(fù)次數(shù). 如下, 小括號(hào)內(nèi)包裹的abc便是一個(gè)分組:
/(abc)+/.test("abc123") == true那么分組有什么用呢? 一般來說, 分組是為了方便的表示重復(fù)次數(shù), 除此之外, 還有一個(gè)作用就是用于捕獲, 請(qǐng)往下看.
捕獲性分組捕獲性分組, 通常由一對(duì)小括號(hào)加上子表達(dá)式組成. 捕獲性分組會(huì)創(chuàng)建反向引用, 每個(gè)反向引用都由一個(gè)編號(hào)或名稱來標(biāo)識(shí), js中主要是通過 $+編號(hào) 或者 +編號(hào) 表示法進(jìn)行引用. 如下便是一個(gè)捕獲性分組的例子.
var color = "#808080"; var output = color.replace(/#(d+)/,"$1"+"~~");//自然也可以寫成 "$1~~" console.log(RegExp.$1);//808080 console.log(output);//808080~~以上, (d+) 表示一個(gè)捕獲性分組, "RegExp.$1" 指向該分組捕獲的內(nèi)容. $+編號(hào) 這種引用通常在正則表達(dá)式之外使用. +編號(hào) 這種引用卻可以在正則表達(dá)式中使用, 可用于匹配不同位置相同部分的子串.
var url = "www.google.google.com"; var re = /([a-z]+).1/; console.log(url.replace(re,"$1"));//"www.google.com"以上, 相同部分的"google"字符串只被替換一次.
非捕獲性分組非捕獲性分組, 通常由一對(duì)括號(hào)加上"?:"加上子表達(dá)式組成, 非捕獲性分組不會(huì)創(chuàng)建反向引用, 就好像沒有括號(hào)一樣. 如下:
var color = "#808080"; var output = color.replace(/#(?:d+)/,"$1"+"~~"); console.log(RegExp.$1);//"" console.log(output);//$1~~以上, (?:d+) 表示一個(gè)非捕獲性分組, 由于分組不捕獲任何內(nèi)容, 所以, RegExp.$1 就指向了空字符串.
命名分組
同時(shí), 由于$1 的反向引用不存在, 因此最終它被當(dāng)成了普通字符串進(jìn)行替換.
實(shí)際上, 捕獲性分組和無捕獲性分組在搜索效率方面也沒什么不同, 沒有哪一個(gè)比另一個(gè)更快.語法: (?
...) 命名分組也是捕獲性分組, 它將匹配的字符串捕獲到一個(gè)組名稱或編號(hào)名稱中, 在獲得匹配結(jié)果后, 可通過分組名進(jìn)行獲取. 如下是一個(gè)python的命名分組的例子.
import re data = "#808080" regExp = r"#(?Pd+)" replaceString = "g " + "~~" print re.sub(regExp,replaceString,data) # 808080~~ python的命名分組表達(dá)式與標(biāo)準(zhǔn)格式相比, 在 ? 后多了一大寫的 P 字符, 并且python通過“g<命名>"表示法進(jìn)行引用. (如果是捕獲性分組, python通過"g<編號(hào)>"表示法進(jìn)行引用)
與python不同的是, javaScript 中并不支持命名分組.
固化分組固化分組, 又叫原子組.
語法: (?>...)
如上所述, 我們?cè)谑褂梅秦澙纺J綍r(shí), 匹配過程中可能會(huì)進(jìn)行多次的回溯, 回溯越多, 正則表達(dá)式的運(yùn)行效率就越低. 而固化分組就是用來減少回溯次數(shù)的.
實(shí)際上, 固化分組(?>…)的匹配與正常的匹配并無分別, 它并不會(huì)改變匹配結(jié)果. 唯一的不同就是: 固化分組匹配結(jié)束時(shí), 它匹配到的文本已經(jīng)固化為一個(gè)單元, 只能作為整體而保留或放棄, 括號(hào)內(nèi)的子表達(dá)式中未嘗試過的備用狀態(tài)都會(huì)被放棄, 所以回溯永遠(yuǎn)也不能選擇其中的狀態(tài)(因此不能參與回溯). 下面我們來通過一個(gè)例子更好地理解固化分組.
假如要處理一批數(shù)據(jù), 原格式為 123.456, 因?yàn)楦↑c(diǎn)數(shù)顯示問題, 部分?jǐn)?shù)據(jù)格式會(huì)變?yōu)?23.456000000789這種, 現(xiàn)要求只保留小數(shù)點(diǎn)后2~3位, 但是最后一位不能為0, 那么這個(gè)正則怎么寫呢?
var str = "123.456000000789"; str = str.replace(/(.dd[1-9]?)d*/,"$1"); //123.456以上的正則, 對(duì)于"123.456" 這種格式的數(shù)據(jù), 將白白處理一遍. 為了提高效率, 我們將正則最后的一個(gè)"*"改為"+". 如下:
var str = "123.456"; str = str.replace(/(.dd[1-9]?)d+/,"$1"); //123.45此時(shí), "dd[1-9]?" 子表達(dá)式, 匹配是 "45", 而不是 "456", 這是因?yàn)檎齽t末尾使用了"+", 表示末尾至少要匹配一個(gè)數(shù)字, 因此末尾的子表達(dá)式"d+" 匹配到了 "6". 顯然 "123.45" 不是我們期望的匹配結(jié)果, 那我們應(yīng)該怎么做呢? 能否讓 "[1-9]?" 一旦匹配成功, 便不再進(jìn)行回溯, 這里就要用到我們上面說的固化分組.
"(.dd(?>[1-9]?))d+" 便是上述正則的固化分組形式. 由于字符串 "123.456" 不滿足該固化分組的正則, 所以, 匹配會(huì)失敗, 符合我們期望.
下面我們來分析下固化分組的正則 (.dd(?>[1-9]?))d+ 為什么匹配不到字符串"123.456".
很明顯, 對(duì)于上述固化分組, 只存在兩種匹配結(jié)果.
情況①: 若 [1-9] 匹配失敗, 正則會(huì)返回 ? 留下的備用狀態(tài). 然后匹配脫離固化分組, 繼續(xù)前進(jìn)到[d+]. 當(dāng)控制權(quán)離開固化分組時(shí), 沒有備用狀態(tài)需要放棄(因固化分組中根本沒有創(chuàng)建任何備用狀態(tài)).
情況②: 若 [1-9] 匹配成功, 匹配脫離固化分組之后, ? 保存的備用狀態(tài)仍然存在, 但是, 由于它屬于已經(jīng)結(jié)束的固化分組, 所以會(huì)被拋棄.
對(duì)于字符串 "123.456", 由于 [1-9] 能夠匹配成功, 所以它符合情況②. 下面我們來還原情況②的執(zhí)行現(xiàn)場(chǎng).
匹配所處的狀態(tài): 匹配已經(jīng)走到了 "6" 的位置, 匹配將繼續(xù)前進(jìn);==>
子表達(dá)式 d+ 發(fā)現(xiàn)無法匹配, 正則引擎便嘗試回溯;==>
查看是否存在備用狀態(tài)以供回溯?==>
"?" 保存的備用狀態(tài)屬于已經(jīng)結(jié)束的固化分組, 所以該備用狀態(tài)會(huì)被放棄;==>
此時(shí)固化分組匹配到的 "6", 便不能用于正則引擎的回溯;==>
嘗試回溯失敗;==>
正則匹配失敗.==>
文本 "123.456" 沒有被正則表達(dá)式匹配上, 符合預(yù)期.
相應(yīng)的流程圖如下:
遺憾的是, javaScript, java 和 python中并不支持固化分組的語法, 不過, 它在php和.NET中表現(xiàn)良好. 下面提供了一個(gè)php版的固化分組形式的正則表達(dá)式, 以供嘗試.
$str = "123.456"; echo preg_replace("/(.dd(?>[1-9]?))d+/","1",$str); //固化分組不僅如此, php還提供了占有量詞優(yōu)先的語法. 如下:
$str = "123.456"; echo preg_replace("/(.dd[1-9]?+)d+/","1",$str); //占有量詞優(yōu)先雖然java不支持固化分組的語法, 但java也提供了占有量詞優(yōu)先的語法, 同樣能夠避免正則回溯. 如下:
String str = "123.456"; System.out.println(str.replaceAll("(.dd[1-9]?+)d+", "$1"));// 123.456值得注意的是: java中 replaceAll 方法需要轉(zhuǎn)義反斜杠.
正則表達(dá)式高階技能-零寬斷言如果說正則分組是寫輪眼, 那么零寬斷言就是萬花筒寫輪眼終極奧義-須佐能乎(這里借火影忍術(shù)打個(gè)比方). 合理地使用零寬斷言, 能夠能分組之不能, 極大地增強(qiáng)正則匹配能力, 它甚至可以幫助你在匹配條件非常模糊的情況下快速地定位文本.
零寬斷言, 又叫環(huán)視. 環(huán)視只進(jìn)行子表達(dá)式的匹配, 匹配到的內(nèi)容不保存到最終的匹配結(jié)果, 由于匹配是零寬度的, 故最終匹配到的只是一個(gè)位置.
環(huán)視按照方向劃分, 有順序和逆序兩種(也叫前瞻和后瞻), 按照是否匹配有肯定和否定兩種, 組合之, 便有4種環(huán)視. 4種環(huán)視并不復(fù)雜, 如下便是它們的描述.
字符 描述 示例 (?:pattern) 非捕獲性分組, 匹配pattern的位置, 但不捕獲匹配結(jié)果.也就是說不創(chuàng)建反向引用, 就好像沒有括號(hào)一樣. "abcd(?:e)匹配"abcde (?=pattern) 順序肯定環(huán)視, 匹配后面是pattern 的位置, 不捕獲匹配結(jié)果. "Windows (?=2000)"匹配 "Windows2000" 中的 "Windows"; 不匹配 "Windows3.1" 中的 "Windows" (?!pattern) 順序否定環(huán)視, 匹配后面不是 pattern 的位置, 不捕獲匹配結(jié)果. "Windows (?!2000)"匹配 "Windows3.1" 中的 "Windows"; 不匹配 "Windows2000" 中的 "Windows" (?<=pattern) 逆序肯定環(huán)視, 匹配前面是 pattern 的位置, 不捕獲匹配結(jié)果. "(?<=Office)2000"匹配 " Office2000" 中的 "2000"; 不匹配 "Windows2000" 中的 "2000" (?pattern) 逆序否定環(huán)視, 匹配前面不是 pattern 的位置, 不捕獲匹配結(jié)果. "(? 非捕獲性分組由于結(jié)構(gòu)與環(huán)視相似, 故列在表中, 以做對(duì)比. 以上4種環(huán)視中, 目前 javaScript 中只支持前兩種, 也就是只支持 順序肯定環(huán)視 和 順序否定環(huán)視. 下面我們通過實(shí)例來幫助理解下:
var str = "123abc789",s; //沒有使用環(huán)視,abc直接被替換 s = str.replace(/abc/,456); console.log(s); //123456789 //使用了順序肯定環(huán)視,捕獲到了a前面的位置,所以abc沒有被替換,只是將3替換成了3456 s = str.replace(/3(?=abc)/,3456); console.log(s); //123456abc789 //使用了順序否定環(huán)視,由于3后面跟著abc,不滿意條件,故捕獲失敗,所以原字符串沒有被替換 s = str.replace(/3(?!abc)/,3456); console.log(s); //123abc789下面通過python來演示下 逆序肯定環(huán)視 和 逆序否定環(huán)視 的用法.
import re data = "123abc789" # 使用了逆序肯定環(huán)視,替換左邊為123的連續(xù)的小寫英文字母,匹配成功,故abc被替換為456 regExp = r"(?<=123)[a-z]+" replaceString = "456" print re.sub(regExp,replaceString,data) # 123456789 # 使用了逆序否定環(huán)視,由于英文字母左側(cè)不能為123,故子表達(dá)式[a-z]+捕獲到bc,最終bc被替換為456 regExp = r"(?需要注意的是: python 和 perl 語言中的 逆序環(huán)視 的子表達(dá)式只能使用定長(zhǎng)的文本. 比如將上述 "(?<=123)" (逆序肯定環(huán)視)子表達(dá)式寫成 "(?<=[0-9]+)", python解釋器將會(huì)報(bào)錯(cuò): "error: look-behind requires fixed-width pattern".
場(chǎng)景回顧 獲取html片段假如現(xiàn)在, js 通過 ajax 獲取到一段 html 代碼如下:
var responseText = "";現(xiàn)我們需要替換img標(biāo)簽的src 屬性中的 "dev"字符串 為 "test" 字符串.
① 由于上述 responseText 字符串中包含至少兩個(gè)子字符串 "dev", 顯然不能直接 replace 字符串 "dev"為 "test".
② 同時(shí)由于 js 中不支持逆序環(huán)視, 我們也不能在正則中判斷前綴為 "src="", 然后再替換"dev".
③ 我們注意到 img 標(biāo)簽的 src 屬性以 ".png" 結(jié)尾, 基于此, 就可以使用順序肯定環(huán)視. 如下:
var reg = /dev(?=[^"]*png)/; //為了防止匹配到第一個(gè)dev, 通配符前面需要排除單引號(hào)或者是尖括號(hào) var str = responseText.replace(reg,"test"); console.log(str);//當(dāng)然, 以上不止順序肯定環(huán)視一種解法, 捕獲性分組同樣可以做到. 那么環(huán)視高級(jí)在哪里呢? 環(huán)視高級(jí)的地方就在于它通過一次捕獲就可以定位到一個(gè)位置, 對(duì)于復(fù)雜的文本替換場(chǎng)景, 常有奇效, 而分組則需要更多的操作.
千位分割符千位分隔符, 顧名思義, 就是數(shù)字中的逗號(hào). 參考西方的習(xí)慣, 數(shù)字之中加入一個(gè)符號(hào), 避免因數(shù)字太長(zhǎng)難以直觀的看出它的值. 故而數(shù)字之中, 每隔三位添加一個(gè)逗號(hào), 即千位分隔符.
那么怎么將一串?dāng)?shù)字轉(zhuǎn)化為千位分隔符形式呢?
var str = "1234567890"; (+str).toLocaleString();//"1,234,567,890"如上, toLocaleString() 返回當(dāng)前對(duì)象的"本地化"字符串形式.
如果該對(duì)象是Number類型, 那么將返回該數(shù)值的按照特定符號(hào)分割的字符串形式.
如果該對(duì)象是Array類型, 那么先將數(shù)組中的每項(xiàng)轉(zhuǎn)化為字符串, 然后將這些字符串以指定分隔符連接起來并返回.
toLocaleString 方法特殊, 有本地化特性, 對(duì)于天朝, 默認(rèn)的分隔符是英文逗號(hào). 因此使用它恰好可以將數(shù)值轉(zhuǎn)化為千位分隔符形式的字符串. 如果考慮到國(guó)際化, 以上方法就有可能會(huì)失效了.
我們嘗試使用環(huán)視來處理下.
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}" 表示連續(xù)3位數(shù)字.
"([0-9]{3})+" 表示連續(xù)3位數(shù)字至少出現(xiàn)一次或更多次.
"([0-9]{3})+$" 表示連續(xù)3的正整數(shù)倍的數(shù)字, 直到字符串末尾.
那么 (?=([0-9]{3})+$) 就表示匹配一個(gè)零寬度的位置, 并且從這個(gè)位置到字符串末尾, 中間擁有3的正整數(shù)倍的數(shù)字.
正則表達(dá)式使用全局匹配g, 表示匹配到一個(gè)位置后, 它會(huì)繼續(xù)匹配, 直至匹配不到.
將這個(gè)位置替換為逗號(hào), 實(shí)際上就是每3位數(shù)字添加一個(gè)逗號(hào).
當(dāng)然對(duì)于字符串"123456"這種剛好擁有3的正整數(shù)倍的數(shù)字的, 當(dāng)然不能在1前面添加逗號(hào). 那么使用 (?!^) 就指定了這個(gè)替換的位置不能為起始位置.
千位分隔符實(shí)例, 展示了環(huán)視的強(qiáng)大, 一步到位.
正則表達(dá)式在JS中的應(yīng)用 ES6對(duì)正則的擴(kuò)展ES6對(duì)正則擴(kuò)展了又兩種修飾符(其他語言可能不支持):
y (粘連sticky修飾符), 與g類似, 也是全局匹配, 并且下一次匹配都是從上一次匹配成功的下一個(gè)位置開始, 不同之處在于, g修飾符只要剩余位置中存在匹配即可, 而y修飾符確保匹配必須從剩余的第一個(gè)位置開始.
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如上, 由于第二次匹配的開始位置是下標(biāo)3, 對(duì)應(yīng)的字符串是 "_", 而使用y修飾符的正則對(duì)象r2, 需要從剩余的第一個(gè)位置開始, 所以匹配失敗, 返回null.
正則對(duì)象的 sticky 屬性, 表示是否設(shè)置了y修飾符. 這點(diǎn)將會(huì)在后面講到.
u 修飾符, 提供了對(duì)正則表達(dá)式添加4字節(jié)碼點(diǎn)的支持. 比如 "?" 字符是一個(gè)4字節(jié)字符, 直接使用正則匹配將會(huì)失敗, 而使用u修飾符后, 將會(huì)等到正確的結(jié)果.
var s = "?"; console.log(/^.$/.test(s));//false console.log(/^.$/u.test(s));//trueUCS-2字節(jié)碼有關(guān)字節(jié)碼點(diǎn), 稍微提下. javaScript 只能處理UCS-2編碼(js于1995年5月被Brendan Eich花費(fèi)10天設(shè)計(jì)出來, 比1996年7月發(fā)布的編碼規(guī)范UTF-16早了一年多, 當(dāng)時(shí)只有UCS-2可選). 由于UCS-2先天不足, 造成了所有字符在js中都是2個(gè)字節(jié). 如果是4個(gè)字節(jié)的字符, 將會(huì)默認(rèn)被當(dāng)作兩個(gè)雙字節(jié)字符處理. 因此 js 的字符處理函數(shù)都會(huì)受到限制, 無法返回正確結(jié)果. 如下:
var s = "?"; console.log(s == "uD834uDF06");//true ?相當(dāng)于UTF-16中的0xD834DF06 console.log(s.length);//2 長(zhǎng)度為2, 表示這是4字節(jié)字符幸運(yùn)的是, ES6可以自動(dòng)識(shí)別4字節(jié)的字符.因此遍歷字符串可以直接使用for of循環(huán). 同時(shí), js中如果直接使用碼點(diǎn)表示Unicode字符, 對(duì)于4字節(jié)字符, ES5里是沒辦法識(shí)別的. 為此ES6修復(fù)了這個(gè)問題, 只需將碼點(diǎn)放在大括號(hào)內(nèi)即可.
console.log(s === "u1D306");//false ES5無法識(shí)別? console.log(s === "u{1D306}");//true ES6可以借助大括號(hào)識(shí)別?附: ES6新增的處理4字節(jié)碼的函數(shù)String.fromCodePoint():從Unicode碼點(diǎn)返回對(duì)應(yīng)字符
String.prototype.codePointAt():從字符返回對(duì)應(yīng)的碼點(diǎn)
String.prototype.at():返回字符串給定位置的字符
有關(guān)js中的unicode字符集, 請(qǐng)參考阮一峰老師的 Unicode與JavaScript詳解 .
以上是ES6對(duì)正則的擴(kuò)展. 另一個(gè)方面, 從方法上看, javaScript 中與正則表達(dá)式有關(guān)的方法有:
方法名 compile test exec match search replace split 所屬對(duì)象 RegExp RegExp RegExp String String String String 由上, 一共有7個(gè)與js相關(guān)的方法, 這些方法分別來自于 RegExp 與 String 對(duì)象. 首先我們先來看看js中的正則類 RegExp.
RegExpRegExp 對(duì)象表示正則表達(dá)式, 主要用于對(duì)字符串執(zhí)行模式匹配.
語法: new RegExp(pattern[, flags])
參數(shù) pattern 是一個(gè)字符串, 指定了正則表達(dá)式字符串或其他的正則表達(dá)式對(duì)象.
參數(shù) flags 是一個(gè)可選的字符串, 包含屬性 "g"、"i" 和 "m", 分別用于指定全局匹配、區(qū)分大小寫的匹配和多行匹配. 如果pattern 是正則表達(dá)式, 而不是字符串, 則必須省略該參數(shù).
var pattern = "[0-9]"; var reg = new RegExp(pattern,"g"); // 上述創(chuàng)建正則表達(dá)式對(duì)象,可以用對(duì)象字面量形式代替,也推薦下面這種 var reg = /[0-9]/g;以上, 通過對(duì)象字面量和構(gòu)造函數(shù)創(chuàng)建正則表達(dá)式, 有個(gè)小插曲.
"對(duì)于正則表達(dá)式的直接量, ECMAscript 3規(guī)定在每次它時(shí)都會(huì)返回同一個(gè)RegExp對(duì)象, 因此用直接量創(chuàng)建的正則表達(dá)式的會(huì)共享一個(gè)實(shí)例. 直到ECMAScript 5才規(guī)定每次返回不同的實(shí)例."
所以, 現(xiàn)在我們基本不用擔(dān)心這個(gè)問題, 只需要注意在低版本的非IE瀏覽器中盡量使用構(gòu)造函數(shù)創(chuàng)建正則(這點(diǎn)上, IE一直遵守ES5規(guī)定, 其他瀏覽器的低級(jí)版本遵循ES3規(guī)定).
RegExp 實(shí)例對(duì)象包含如下屬性:
compile
實(shí)例屬性 描述 global 是否包含全局標(biāo)志(true/false) ignoreCase 是否包含區(qū)分大小寫標(biāo)志(true/false) multiline 是否包含多行標(biāo)志(true/false) source 返回創(chuàng)建RegExp對(duì)象實(shí)例時(shí)指定的表達(dá)式文本字符串形式 lastIndex 表示原字符串中匹配的字符串末尾的后一個(gè)位置, 默認(rèn)為0 flags(ES6) 返回正則表達(dá)式的修飾符 sticky(ES6) 是否設(shè)置了y(粘連)修飾符(true/false) compile 方法用于在執(zhí)行過程中改變和重新編譯正則表達(dá)式.
語法: compile(pattern[, flags])
參數(shù)介紹請(qǐng)參考上述 RegExp 構(gòu)造器. 用法如下:
var reg = new RegExp("abc", "gi"); var reg2 = reg.compile("new abc", "g"); console.log(reg);// /new abc/g console.log(reg2);// undefined可見 compile 方法會(huì)改變?cè)齽t表達(dá)式對(duì)象, 并重新編譯, 而且它的返回值為空.
testtest 方法用于檢測(cè)一個(gè)字符串是否匹配某個(gè)正則規(guī)則, 只要是字符串中含有與正則規(guī)則匹配的文本, 該方法就返回true, 否則返回 false.
語法: test(string), 用法如下:
console.log(/[0-9]+/.test("abc123"));//true console.log(/[0-9]+/.test("abc"));//false以上, 字符串"abc123" 包含數(shù)字, 故 test 方法返回 true; 而 字符串"abc" 不包含數(shù)字, 故返回 false.
如果需要使用 test 方法測(cè)試字符串是否完成匹配某個(gè)正則規(guī)則, 那么可以在正則表達(dá)式里增加開始(^)和結(jié)束($)元字符. 如下:
console.log(/^[0-9]+$/.test("abc123"));//false以上, 由于字符串"abc123" 并非以數(shù)字開始, 也并非以數(shù)字結(jié)束, 故 test 方法返回false.
實(shí)際上, 如果正則表達(dá)式帶有全局標(biāo)志(帶有參數(shù)g)時(shí), test 方法還受正則對(duì)象的lastIndex屬性影響,如下:
var reg = /[a-z]+/;//正則不帶全局標(biāo)志 console.log(reg.test("abc"));//true console.log(reg.test("de"));//true var reg = /[a-z]+/g;//正則帶有全局標(biāo)志g console.log(reg.test("abc"));//true console.log(reg.lastIndex);//3, 下次運(yùn)行test時(shí),將從索引為3的位置開始查找 console.log(reg.test("de"));//false該影響將在exec 方法講解中予以分析.
execexec 方法用于檢測(cè)字符串對(duì)正則表達(dá)式的匹配, 如果找到了匹配的文本, 則返回一個(gè)結(jié)果數(shù)組, 否則返回null.
語法: exec(string)
exec 方法返回的數(shù)組中包含兩個(gè)額外的屬性, index 和 input. 并且該數(shù)組具有如下特點(diǎn):
第 0 個(gè)項(xiàng)表示正則表達(dá)式捕獲的文本
第 1~n 項(xiàng)表示第 1~n 個(gè)反向引用, 依次指向第 1~n 個(gè)分組捕獲的文本, 可以使用RegExp.$ + "編號(hào)1~n" 依次獲取分組中的文本
index 表示匹配字符串的初始位置
input 表示正在檢索的字符串
無論正則表達(dá)式有無全局標(biāo)示"g", exec 的表現(xiàn)都相同. 但正則表達(dá)式對(duì)象的表現(xiàn)卻有些不同. 下面我們來詳細(xì)說明下正則表達(dá)式對(duì)象的表現(xiàn)都有哪些不同.
假設(shè)正則表達(dá)式對(duì)象為 reg , 檢測(cè)的字符為 string , reg.exec(string) 返回值為 array.
若 reg 包含全局標(biāo)示"g", 那么 reg.lastIndex 屬性表示原字符串中匹配的字符串末尾的后一個(gè)位置, 即下次匹配開始的位置, 此時(shí) reg.lastIndex == array.index(匹配開始的位置) + array[0].length(匹配字符串的長(zhǎng)度). 如下:
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隨著檢索繼續(xù), array.index 的值將往后遞增, 也就是說, reg.lastIndex 的值也會(huì)同步往后遞增. 因此, 我們也可以通過反復(fù)調(diào)用 exec 方法來遍歷字符串中所有的匹配文本. 直到 exec 方法再也匹配不到文本時(shí), 它將返回 null, 并把 reg.lastIndex 屬性重置為 0.
接著上述例子, 我們繼續(xù)執(zhí)行代碼, 看看上面說的對(duì)不對(duì), 如下所示:
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以上代碼中, 隨著反復(fù)調(diào)用 exec 方法, reg.lastIndex 屬性最終被重置為 0.
問題回顧
在 test 方法的講解中, 我們留下了一個(gè)問題. 如果正則表達(dá)式帶有全局標(biāo)志g, 以上 test 方法的執(zhí)行結(jié)果將受 reg.lastIndex影響, 不僅如此, exec 方法也一樣. 由于 reg.lastIndex 的值并不總是為零, 并且它決定了下次匹配開始的位置, 如果在一個(gè)字符串中完成了一次匹配之后要開始檢索新的字符串, 那就必須要手動(dòng)地把 lastIndex 屬性重置為 0. 避免出現(xiàn)下面這種錯(cuò)誤:
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"]以上代碼, 正確執(zhí)行結(jié)果應(yīng)該是 "123456", 因此建議在第二次執(zhí)行 exec 方法前, 增加一句 "reg.lastIndex = 0;".
若 reg 不包含全局標(biāo)示"g", 那么 exec 方法的執(zhí)行結(jié)果(array)將與 string.match(reg) 方法執(zhí)行結(jié)果完全相同.
Stringmatch, search, replace, split 方法請(qǐng)參考 字符串常用方法 中的講解.
如下展示了使用捕獲性分組處理文本模板, 最終生成完整字符串的過程:
var tmp = "An ${a} a $ 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可這么實(shí)現(xiàn):
var obj = { a:"apple", b:"day", c:"doctor" }; with(obj){ console.log(`An ${a} a $ keeps the ${c} away`); }正則表達(dá)式在H5中的應(yīng)用H5中新增了 pattern 屬性, 規(guī)定了用于驗(yàn)證輸入字段的模式, pattern的模式匹配支持正則表達(dá)式的書寫方式. 默認(rèn) pattern 屬性是全部匹配, 即無論正則表達(dá)式中有無 "^", "$" 元字符, 它都是匹配所有文本.
注: pattern 適用于以下 input 類型:text, search, url, telephone, email 以及 password. 如果需要取消表單驗(yàn)證, 在form標(biāo)簽上增加 novalidate 屬性即可.
正則引擎目前正則引擎有兩種, DFA 和 NFA, NFA又可以分為傳統(tǒng)型NFA和POSIX NFA.
DFA?Deterministic finite automaton 確定型有窮自動(dòng)機(jī)
NFA Non-deterministic finite automaton 非確定型有窮自動(dòng)機(jī)
Traditional NFA
POSIX NFA
DFA引擎不支持回溯, 匹配快速, 并且不支持捕獲組, 因此也就不支持反向引用. 上述awk, egrep命令均支持 DFA引擎.
POSIX NFA主要指符合POSIX標(biāo)準(zhǔn)的NFA引擎, 像 javaScript, java, php, python, c#等語言均實(shí)現(xiàn)了NFA引擎.
有關(guān)正則表達(dá)式詳細(xì)的匹配原理, 暫時(shí)沒在網(wǎng)上看到適合的文章, 建議選讀 Jeffrey Friedl 的 <精通正則表達(dá)式>[第三版] 中第4章-表達(dá)式的匹配原理(p143-p183), Jeffrey Friedl 對(duì)正則表達(dá)式有著深刻的理解, 相信他能夠幫助您更好的學(xué)習(xí)正則.
有關(guān)NFA引擎的簡(jiǎn)單實(shí)現(xiàn), 可以參考文章 基于ε-NFA的正則表達(dá)式引擎 - twoon.
總結(jié)在學(xué)習(xí)正則的初級(jí)階段, 重在理解 ①貪婪與非貪婪模式, ②分組, ③捕獲性與非捕獲性分組, ④命名分組, ⑤固化分組, 體會(huì)設(shè)計(jì)的精妙之處. 而高級(jí)階段, 主要在于熟練運(yùn)用⑥零寬斷言(或環(huán)視)解決問題, 并且熟悉正則匹配的原理.
實(shí)際上, 正則在 javaScript 中的功能不算強(qiáng)大, js 僅僅支持了①貪婪與非貪婪模式, ②分組, ③捕獲性與非捕獲性分組 以及 ⑥零寬斷言中的順序環(huán)視. 如果再稍微熟悉些 js 中7種與正則有關(guān)的方法(compile, test, exec, match, search, replace, split), 那么處理文本或字符串將游刃有余.
正則表達(dá)式, 在文本處理方面天賦異稟, 它的功能十分強(qiáng)大, 很多時(shí)候甚至是唯一解決方案. 正則不局限于js, 當(dāng)下熱門的編輯器(比如Sublime, Atom) 以及 IDE(比如WebStorm, IntelliJ?IDEA) 都支持它. 您甚至可以在任何時(shí)候任何語言中, 嘗試使用正則解決問題, 也許之前不能解決的問題, 現(xiàn)在可以輕松的解決.
附其他語言正則資料:
Python正則表達(dá)式操作指南
java正則表達(dá)式
本文作者: louis
本文簡(jiǎn)介: 本文斷斷續(xù)續(xù)歷時(shí)兩個(gè)月而成, 共計(jì)12k字, 為求簡(jiǎn)潔全面地還原前端場(chǎng)景中正則的使用規(guī)律, 搜集了大量正則相關(guān)資料, 并剔除不少冗余字句, 碼字不易, 喜歡的請(qǐng)點(diǎn)個(gè)贊?或者收藏, 我將持續(xù)保持更新.
原文地址: http://louiszhai.github.io/20...參考文章
Jeffrey Friedl 的 <精通正則表達(dá)式>[第三版]
linux shell 正則表達(dá)式(BREs,EREs,PREs)差異比較
正則表達(dá)式之捕獲組/非捕獲組介紹_正則表達(dá)式_腳本之家
正則表達(dá)式(一) -- 元字符 - 逆心 - 博客園
正則表達(dá)式詳解 - guisu,程序人生。 逆水行舟,不進(jìn)則退。 - 博客頻道 - CSDN.NET
正則表達(dá)式之固化分組 - taek - 博客園
正則表達(dá)式之 貪婪與非貪婪模式詳解(概述)_正則表達(dá)式_腳本之家
JAVASCRIPT 正則表達(dá)式學(xué)習(xí)-->基礎(chǔ)與零寬斷言(轉(zhuǎn)自司徒正美) - 隨風(fēng)之羽 - 博客頻道 - CSDN.NET
Unicode與JavaScript詳解 - 阮一峰的網(wǎng)絡(luò)日志
0-9 ?
fnrtv ?
A-Za-z0-9_ ?
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/88131.html
摘要:特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 本以為自己收藏的站點(diǎn)多,可以很快搞定,沒想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補(bǔ)充。有錯(cuò)誤的地方,還請(qǐng)斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會(huì)及時(shí)更新,平時(shí)業(yè)務(wù)工作時(shí)也會(huì)不定期更...
摘要:本文內(nèi)容共正則表達(dá)式火拼系列正則表達(dá)式回溯法原理學(xué)習(xí)正則表達(dá)式,是需要懂點(diǎn)兒匹配原理的。正則表達(dá)式迷你書問世了讓幫你生成和解析參數(shù)字符串最全正則表達(dá)式總結(jié)驗(yàn)證號(hào)手機(jī)號(hào)中文郵編身份證地址等是正則表達(dá)式的縮寫,作用是對(duì)字符串執(zhí)行模式匹配。 JS 的正則表達(dá)式 正則表達(dá)式 一種幾乎可以在所有的程序設(shè)計(jì)語言里和所有的計(jì)算機(jī)平臺(tái)上使用的文字處理工具。它可以用來查找特定的信息(搜索),也可以用來查...
摘要:好多編輯器例如等都支持這樣的語法來快速的編寫代碼如何優(yōu)雅地使用把標(biāo)簽放在結(jié)束標(biāo)簽之后結(jié)束標(biāo)簽之前的差別什么是響應(yīng)式設(shè)計(jì)怎樣進(jìn)行 書籍 《JavaScriptDOM編程藝術(shù)》《JavaScript高級(jí)程序設(shè)計(jì)》《JavaScript框架設(shè)計(jì)》《JavaScript專家編程》《JavaScript Ninjia》《JavaScript語言精粹(修訂版)》《JavaScript設(shè)計(jì)模式》《J...
摘要:好多編輯器例如等都支持這樣的語法來快速的編寫代碼如何優(yōu)雅地使用把標(biāo)簽放在結(jié)束標(biāo)簽之后結(jié)束標(biāo)簽之前的差別什么是響應(yīng)式設(shè)計(jì)怎樣進(jìn)行 書籍 《JavaScriptDOM編程藝術(shù)》《JavaScript高級(jí)程序設(shè)計(jì)》《JavaScript框架設(shè)計(jì)》《JavaScript專家編程》《JavaScript Ninjia》《JavaScript語言精粹(修訂版)》《JavaScript設(shè)計(jì)模式》《J...
摘要:好多編輯器例如等都支持這樣的語法來快速的編寫代碼如何優(yōu)雅地使用把標(biāo)簽放在結(jié)束標(biāo)簽之后結(jié)束標(biāo)簽之前的差別什么是響應(yīng)式設(shè)計(jì)怎樣進(jìn)行 書籍 《JavaScriptDOM編程藝術(shù)》《JavaScript高級(jí)程序設(shè)計(jì)》《JavaScript框架設(shè)計(jì)》《JavaScript專家編程》《JavaScript Ninjia》《JavaScript語言精粹(修訂版)》《JavaScript設(shè)計(jì)模式》《J...
閱讀 1872·2021-09-28 09:35
閱讀 1207·2019-08-30 15:54
閱讀 1732·2019-08-30 15:44
閱讀 3428·2019-08-30 14:09
閱讀 576·2019-08-29 14:05
閱讀 2754·2019-08-28 17:53
閱讀 2101·2019-08-26 13:41
閱讀 1787·2019-08-26 13:26