JS理解正则表达式
原文:理解正則表達式
在我初學正則表達式的時候,走了一些彎路,強行記憶了很多符號和用法。
等到我有更深入的理解的時候我發現,從翻譯和概念的角度上切入,學習起來會順暢得多。
本文以JavaScript里的正則表達式為例,講解其中的關鍵要素。希望能幫助到初學者。
注:也只限于闡述關鍵要素,不會事無巨細地展開。
何為正則表達式?
在中文語境里,「正則」兩個字有點讓人發怵,仿佛高深數學或物理中的「正則化」和「歸一化」,抽象而難懂。
其實放到英文里,它是regular expression,而regular有「規律、規范、整齊、合格、正規」等意味,「正則」只是其中一種翻譯。
不把它翻譯成一個詞組,而翻譯成一句話,大致是:表達規范和規則的句子。
這里的規范和規則,指的是一個字符串的形式規則。
至于JavaScript里的 RegExp 構造函數,是Regular Expression的前三個字母縮寫。
正則表達式的格式
在JavaScript里,正則表達式有兩個構造方式,一個是通過RegExp這個構造函數創建實例,另一個是正則表達式字面量寫法。
var regexp1 = new RegExp('hello regular expression')
var regexp2 = /hello regular expression/
// test 方法,測試給定的字符串是否符合正則表達式所描述的字符串格式
regexp1.test('hello regular expression') // -> true
regexp1.test('hello word') // -> false
// exec 方法,是 execute 這個單詞的縮寫,「執行」。返回給定的字符串中符合「正則表達式所描述的字符串格式」的部分
regexp2.exec('hello regular expression') // 返回 'hello regular expression'
regexp2.exec('hello regular expression, more words') // 只返回 'hello regular expression',其它部分不匹配
正則表達式里的元字符
元字符聽起來也很抽象,其實換個例子就容易理解:學習如何學習,叫元學習;關于知識的知識,叫元知識。
元字符,則是描述字符的字符,比如,數字,字母,空格,換行等。
元編程,就是能生成代碼的代碼,在 Javascript 構造符合語法的字符串,放到eval(code)里運行一下,你就在元編程了。
然后看看元字符、元編程的英文:meta-character與meta-programming,對 meta 長個記性,怯魅。
列舉幾個元字符。元字符大多以反斜杠開頭 ,因為前面展示的「正則表達式字面量」寫法里,用的是兩個斜杠包裹,所以得用反斜杠或其他標識符。
d,匹配單個數字;d 是 digit 這個單詞的縮寫,它的中文意思就是「數字」w,匹配單個單詞字符,w 是 word 的縮寫,就是字母 a-z,數字 0-9,不包括逗號、句號、加減乘除號、括號等。s,匹配單個空白字符,s 是 space 的縮寫,就是空白的意思。n,匹配換行符,n 是 newline 的縮寫,中文就是換行。r,匹配回車符,r 就是 return 的縮寫,回車在這里就是它的中文意思。t,匹配制表符,就是 tab 鍵打出來的一串用以縮進的空白字符,tab 是 tabel 的縮寫,table 就有表格和制表的意思。b,匹配單詞邊界,b 是 boundary 的縮寫,中文就是邊界的意思。
如你所見,所謂的元字符,就是反斜杠加單詞縮寫,來表征某個字符類型。這就是它們的設計原則。
正則表達式里的量詞
元字符大多只能表示單個字符的類型。
我們還需要量詞,以表示「有,有0到多個,有至少一個,有n個以上,有n到m個,以某個字符開頭,以某個字符結尾等」。
這時你可以停下來,稍作思考,讓你來設計,你會設計成什么樣?
Javascript的設計如下:
n+,至少1個 n 類型的字符n*,0到多個 n 類型的字符n?,0 或 1 個 n 類型的字符n{X},X 個 n 類型的字符n{X,Y},X 到 Y 個 n 類型的字符n{X,},至少 X 個 n 類型的字符n$,以 n 類型的字符結尾^n,以 n 類型的字符開頭
如你所見,大致是一些類似數學里表達區間的意思。
正則表達式里的表達式
你可以戲謔地說它是「元表達式」。
其實,它們也是描述范圍的,只是不是所有范圍都是關于某個字符類型n 的數量和出現位置,有些范圍跟多個字符組成的集合有關。
比如,在這幾個字符類型之內,在這幾個字符類型之外的,便利地表示 26 個字母,便利地表示 10 個數字字符。
Javascript的設計如下:
[abc],匹配單個字符,它是abc的集合的元素[^abc],匹配單個字符,它不是abc的集合的元素[0-9],匹配單個字符,它是從0到9這個集合的元素[a-z],匹配單個字符,它是26 字母這個集合的元素(red|blue|green),匹配多個連續字符,它是 red blue green 這三個詞的集合的元素
小試牛刀
匹配一個電話號碼,形式如 020-88813243。
簡單版本,(開頭)三個數字+一個橫杠+八個數字(結尾),就是/^d{3}-d{8}$/。
需求變化,只匹配 020 開頭的電話號碼,就是/^020-d{8}$/。
需求變化,支持分機,分機為 5 個數字,加后綴,就是/^020-d{8}-d{5}$/。
需求變化,電話號碼可以是7個,用區間量詞,就是/^020-d{7,8}-d{5}$/。
需求變化,有可能沒有分機,用區間量詞,中括號包裹住分機為一組,后面加個問號,表示0或多個,就是/^020-d{7,8}(-d{5})?$/。
需求變化,區隔符可能是橫杠,也可能是星號或空格,用集合表達式,就是/^020[-*s]d{7,8}([-*s]d{5})?$/
結語
在我們理解了正則表達式的概念和設計思路之后,剩下的,就是查文檔和尋找模式的工作了。
實在有難題,網上也可以搜索到現成的堅實的正則表達式可用。這里面的門道還是很多的,在此我們入個門,打個基礎即可。
補充:正則表達式簡要學習
元字符
元字符是功能性的匹配符號, 如:
b 單詞的開頭或結尾,也就是單詞的分界處
* 匹配任意數量的字符
. 匹配除了換行之外的所有字符
d 匹配0到9單個數字
s 匹配任意的空白符,包括空格,制表符(Tab),換行符,中文全角空格等
w匹配 字母 或 數字 或 下劃線 或 漢字 等
^ 匹配字符串的開始
$ 匹配字符串的結束
字符轉意
查找元字符本身的話,比如你查找.,或者*,就出現了問題:你沒辦法指定它們,因為它們會被解釋成別的意思。這時你就得使用來取消這些字符的特殊意義。因此,你應該使用.和*。當然,要查找本身,你也得用\
字符類
[ ] 集合查找 ,比如 [abcde] 表示匹配里面包含的字符 , 常見的[0-9]和d等價, 即匹配一位數字, [a-z0-9A-Z_]也完全等同于w(如果只考慮英文的話)
(?0d{2}[) -]?d{8}首先是一個轉義字符(,它能出現0次或1次?,然后是一個0,后面跟著2個數字d{2},然后是)或-或空格中的一個,它出現0次或1次?,最后是8個數字d{8}
分枝條件
上面那個表達式也能匹配010)12345678或(022-87654321這樣的“不正確”的格式。
正則表達式里的分枝條件指的是有幾種規則,如果滿足其中任意一種規則都應該當成匹配,具體方法是用|把不同的規則分隔開。
0d{2}-d{8}|0d{3}-d{7}表示0開頭接兩位數字,-后面連著8位數的電話號碼,比如020-12345678 或者 0開頭接三位數字,-后面連著7位數的電話號碼,比如0751-1234567
使用分枝條件時,要注意各個條件的順序。原因是匹配分枝條件時,將會從左到右地測試每個條件,如果滿足了某個分枝的話,就不會去再管其它的條件了。
分組
如果想要重復多個字符又,你可以用小括號來指定子表達式(也叫做分組)
(d{1,3}.){3}d{1,3}是一個簡單的IP地址匹配表達式。要理解這個表達式,請按下列順序分析它:d{1,3}匹配1到3位的數字,(d{1,3}.){3}匹配三位數字加上一個英文句號(這個整體也就是這個分組)重復3次,最后再加上一個一到三位的數字(d{1,3})。
正則表達式中并不提供關于數學的任何功能,所以只能使用冗長的分組,選擇,字符類來描述一個正確的IP地址:((2[0-4]d|25[0-5]|[01]?dd?).){3}(2[0-4]d|25[0-5]|[01]?dd?)
反義
有時需要查找不屬于某個能簡單定義的字符類的字符。比如想查找除了數字以外,其它任意字符都行的情況,這時需要用到反義:
| 符號 | 表示 |
|---|---|
| W | 匹配任意不是字母,數字,下劃線,漢字的字符 |
| S | 匹配任意不是空白符的字符 |
| D | 匹配任意非數字的字符 |
| B | 匹配不是單詞開頭或結束的位置 |
| [ ^x ] | 匹配除了x以外的任意字符 |
| [ ^aeiou ] | 匹配除了aeiou這幾個字母以外的任意字符 |
例子:
S+匹配不包含空白符的字符串。
<a[^>]+>匹配用尖括號括起來的以a開頭的字符串
貪婪與懶惰
當正則表達式中包含能接受重復的限定符時,通常的行為是(在使整個表達式能得到匹配的前提下)匹配盡可能多的字符。以這個表達式為例:a.*b,它將會匹配最長的以a開始,以b結束的字符串。如果用它來搜索aabab的話,它會匹配整個字符串aabab。這被稱為貪婪匹配。
有時,我們更需要懶惰匹配,也就是匹配盡可能少的字符。前面給出的限定符都可以被轉化為懶惰匹配模式,只要在它后面加上一個問號?。這樣.*?就意味著匹配任意數量的重復,但是在能使整個匹配成功的前提下使用最少的重復。
a.*?b匹配最短的,以a開始,以b結束的字符串。如果把它應用于aabab的話,它會匹配aab(第一到第三個字符)和ab(第四到第五個字符)
為什么第一個匹配是aab(第一到第三個字符)而不是ab(第二到第三個字符)?簡單地說,因為正則表達式有另一條規則,比懶惰/貪婪規則的優先級更高:最先開始的匹配擁有最高的優先權
| 符號 | 表示 |
|---|---|
| *? | 重復任意次,但盡可能少重復 |
| +? | 重復1次或更多次,但盡可能少重復 |
| ?? | 重復0次或1次,但盡可能少重復 |
| {n,m}? | 重復n到m次,但盡可能少重復 |
| {n,}? | 重復n次以上,但盡可能少重復 |
參考: http://deerchao.net/tutorials/regex/regex.htm
總結
- 上一篇: python有多少库存_库存究竟多少才算
- 下一篇: 江门在哪里