3.'1234567'.match(/d{1,3}/g)
的結果。/^d{3}/
。/[^abc]/
。["123", "456", "7"]
。/b(?!th)w+b/
。/^(?=.*d).{4,8}$/
。首先.{4,8} 表示與包含 4-8 個字符的字符串匹配;然后.*表示單個字符(除換行符 n 外)零次或多次,且后面跟著一個數字,注意(?=)只匹配一個位置。/[u4e00-u9fa5]/
。當然,可能答案不唯一,不必較真啦~ 主要目的是回憶熟悉一下語法~
要想在復雜性和完整性之間取得平衡,一個重要因素是要了解將要搜索的文本。
好的正則表達式:
有時候處理各種極端情況會降低成本/收益的比例。所以某些情況下,不完全依賴正則表達式完成全部工作,比如某些字段用子表達式()括起來,讓內存記憶下來,然后再用其他程序來驗證。
不過本文還是從學習正則的角度出發,全部依賴正則表達式來寫的哇~~
正則表達式:/^$[0-9]+(.[0-9][0-9])?$/
。
分為四部分:
^$
以美元符號開頭。[0-9]+
至少包含一個數字。(.[0-9][0-9])?
由一個點和兩位數組成,匹配0次或1次,因為可能是整數或者是小數。$
最后的$表示以數字結尾的。缺點:不能匹配$1,000
方法一:分類邏輯為第一個數字(0、1、2),可以分為三部分:上午 00點到09點(0可選);白天10到19點;晚上20到23點。
因此有三個多選分支,得到的結果為:
1 |
0?[0-9]|1[0-9]|2[0-3] |
還可以優化一下,合并前面的兩個多選分支,得到:
1 |
[01]?[0-9]|2[0-3] |
方法二:分類邏輯為第二個數字,可以分為兩部分:[0-3]和[4-9]。為什么這么分?看看下面這個圖就知道了,[0-3]多了一行(以2為第一個數字):
因此有兩個多選分支,結果為:
1 |
[012]?[0-3]|[01]?[4-9] |
分鐘數比較簡單,第一個數范圍在0-5之間,第二個數在0-9之間,因此得到分鐘數為:
1 |
[0-5][0-9] |
小時部分用(?:)包起來,起到一個分組的作用,且不保存匹配項;
冒號、分鐘數拼起來;
最后加上一個分界b
表示單詞的開始或結束,得到最終的結果:
1 2 3 |
/b(?:[01]?[0-9]|2[0-3]):[0-5][0-9]b/ // 或者 /b(?:[012]?[0-3]|[01]?[4-9]):[0-5][0-9]b/ |
1 2 3 |
var reg = /b(?:[01]?[0-9]|2[0-3]):[0-5][0-9]b/; '現在是09:49點'.match(reg); // ["09:49"] '現在是009:490點'.match(reg); // null |
其實這個結果不能說完全正確,首先你要明白這個正則用在什么地方,比如是數據驗證或者
復雜的字符串搜尋替換。
情景一:填寫表單中的字符串必須為24小時制的時間,那么可能第一個b
需要改成^,第二個b
改成$
。
情景二:用于復雜的字符串搜尋替換時,可能也會匹配這樣子的字符串如’跑步用時19:50’,明顯的,’19:50’表示19分50秒,而不是表示24小時制的時間19點50分。
IP地址的規則:點號分開的四個字段,每個字段在0-255之間。
如果一個字段是一個數或兩個數,肯定是在0-255的范圍內的;
如果三位數,那么以0或者1開頭的三位數也是合法的,即000-199。
從上面的陳述中我們就可以得到三個多選分支:
1 |
d|dd|[01]dd |
我們稍微合并一下這三個多選分支,得到:
1 |
[01]?dd? |
我們再來看以2開頭的三位數:
第二位數小于5的時候,第三位數范圍[0-9]都可以;第二位數等于5的時候,第三位數范圍[0-5] ,因此得到兩個多選分支:
1 |
2[0-4]d|25[0-5] |
前兩步合并起來,得到一個字段0-255的表示方法:
1 |
[01]?dd?|2[0-4]d|25[0-5] |
四個字段合并起來,IP地址正則如下:
1 |
/^(?:[01]?dd?|2[0-4]d|25[0-5]).(?:[01]?dd?|2[0-4]d|25[0-5]).(?:[01]?dd?|2[0-4]d|25[0-5]).(?:[01]?dd?|2[0-4]d|25[0-5])$/ |
點號要轉義一下,^和$需要加上,否則可能匹配52123.3.22.993,因為其中的123.3.22.99是符合的。(?:)起到分組的作用,且不保存匹配項。
一些測試結果:
1 2 3 4 5 6 |
var reg = /^(?:[01]?dd?|2[0-4]d|25[0-5]).(?:[01]?dd?|2[0-4]d|25[0-5]).(?:[01]?dd?|2[0-4]d|25[0-5]).(?:[01]?dd?|2[0-4]d|25[0-5])$/; '123.11.22.33.44'.match(reg); // null '52123.3.22.993'.match(reg); // null '123.11.22.33'.match(reg); // ["123.11.22.33"] '0.0.0.0'.match(reg); // ["0.0.0.0"] |
雖然0.0.0.0是合法的,但它是非法的IP地址,使用正則的否定順序環視功能(零寬負向先行斷言),可加上(?!0+.0+.0+.0+$) :
1 2 3 4 |
var reg = /^(?!0+.0+.0+.0+$)(?:[01]?dd?|2[0-4]d|25[0-5]).(?:[01]?dd?|2[0-4]d|25[0-5]).(?:[01]?dd?|2[0-4]d|25[0-5]).(?:[01]?dd?|2[0-4]d|25[0-5])$/; '123.11.22.33'.match(reg); // ["123.11.22.33"] '0.0.0.0'.match(reg); // null |
/*
和*/
之間的css注釋。描述:起始分隔符和結束分隔符都是"
,且正文中容許出現轉義之后的引號"
。
簡單情況分析:
舉例:匹配類似 I "start "x3" end" U
文本的 "start "x3" end"
引文字符串,注意"
屬于轉義引號。
"
。[^"]
表示不是引號的其他任意字符。"
前面有反斜線,且被反斜線轉義,則也屬于正文。例如start"
引號的前面有一個反斜線,那么這個引號也屬于正文。即(?<=)”表示匹配一個引號,它的前面有一個,注意正則的反斜線也要用來轉義一下,因為是特殊字符。用非捕獲分組(?:)
將[^"]|(?<=)"
括起來,給個量詞*
,表示匹配正文0次或多次。
因此可以寫出正則表達式: /"(?:[^"]|(?<=)")*"/
注意:ES7才支持逆序環視(?<=)
驗證正則:/"(?:[^"]|(?<=)")*"/
1 2 3 4 5 |
'I "start "x3" end" U'.match(/"(?:[^"]|(?<=)")*"/); // 結果: [""start ""] 'I "start "x3" end" U'.match(/"(?:[^"]|(?<=)")*"/); // 結果:[""start "x3" end""] |
為什么第2個才是對的呢?我們看一下返回的input屬性就了解了:
驗證正則:/"(?:[^"]|(?<=)")*"/
1 2 3 4 |
'I "start "x3" end" U'.match(/"(?:[^"]|(?<=)")*"/); // 結果與期望不符合:[""start "x3" end""] // 期望:[""start"x3"] // 注意返回的input屬性為:"I "start "x3" end" U" |
引號”前面有反斜線,但是這個反斜線不是轉義引號的,那么引號就不應該屬于正文,而是屬于結束分隔符。
什么情況反斜線不轉義引號呢?
這個反斜線本身就是被轉義的情況。
上面的結果按照預期結果應該返回 [""start"x3"]
,但是現在多了end"
。
因此驗證這個正則表達式不正確。
也就是說,正文中可出現轉義的字符,因此得出正則.
,注意第一個表示轉義第二個,點表示匹配除換行符 n 之外的任何單個字符),例如可以匹配+
或者。而且轉義的字符已經包含了"
的情況,因此正則(?<=)"
可以不用寫了,且替換成.
。
因此改正后的正則:/"(?:.|[^"])*"/
你可能注意到了,我把[^”]和.的位置調換一下,后面的驗證3會講到為什么要這么做。
驗證正則:/"(?:.|[^"])*"/
和 /"(?:[^"]|.)*"/
1 2 3 4 5 6 7 8 9 |
'I "start "x3" end" U'.match(/"(?:.|[^"])*"/); // 結果與期望符合:[""start "x3""] // input: "I "start "x3" end" U" // [^"]和.的位置調換 'I "start "x3" end" U'.match(/"(?:[^"]|.)*"/); // 結果與期望不符合:[""start ""] // 期望:[""start"x3"] // input: "I "start "x3" end" U" |
[^"]
和.
的位置調換后,結果與期望不符合。那是因為[^"]
匹配start
后,遇到緊接著的"
不匹配,交給后面的多選分支.
,也不匹配,又剛好結束分隔符是"
,導致匹配成功,結束匹配。
因此兩個正則之間 正確的正則是 /"(?:.|[^"])*"/
驗證:/"(?:.|[^"])*"/
1 2 3 4 |
'I "start "x3" end U'.match(/"(?:.|[^"])*"/); // 結果與期望不符合:[""start "x3""] // 注意end后面少了",期望結果是null,不匹配 // input: "I "start "x3" end U" |
上面的字符串 "start"x3"
其實是沒有結束分隔符的,但是還是匹配了。那是因為正則[^"]
和.
一起作用,導致匹配到了文本U末尾,后續想找結束分隔符的時候,結果卻找不到,所以只能回溯文本去找結束分隔符,最后找到了 x3
后面的引號,匹配成功,結束匹配。
回溯會導致不期望的結果,由于是卡在多選分支上出錯的,因此猜測多選分支|
匹配內容出現重疊。
你想想,如果符合正文的反斜線,不是以[^"]
方式匹配,而是以.
的方式匹配,那就不會把好好的"
拆開來匹配了。
綜上所述,一定要讓反斜線是以.
的方式匹配,字符串里的反斜桿不能以[^"]
方式匹配。
因此將[^"]
改成[^"]
。這樣子就可以確保正確識別正文特殊的"
和結束分隔符"
了。
注意:很多字符在[]都會失去本來的意義,但是反斜杠字符 仍為轉義字符。若要匹配反斜杠字符,請使用兩個反斜杠 。
改正的正則:/"(?:.|[^"])*"/
驗證:/"(?:.|[^"])*"/
1 2 3 4 5 6 7 8 |
'I "start "x3" end U'.match(/"(?:.|[^"])*"/); // 結果與期望符合:null // input: "I "start "x3" end" U" 'I "start "x3" end" U'.match(/"(?:.|[^"])*"/); // 結果與期望符合:[""start "x3" end""] // input: "I "start "x3" end" U" |
為了優化,我們可以把[^"]
放在前面,因為普通字符的匹配可能性更大。
注意:優化正則提高效率最需要考慮的問題:改動是否會影響匹配。只有在排序與匹配成功無關時才不會影響準確性,才能重新安排多選分支的順序。
優化后的正則:/"(?:[^"]|.)*"/