作為一名攻城獅,對正則表達式一定不會很陌生,但在平時開發中有時依然會遇到這樣或那樣的問題。本文從基礎出發,本著讓初學者入門,高手溫故的初衷,相對系統性的介紹正了則相關知識。如有不準確的地方,歡迎吐槽
原文地址:http://www.lxweimin.com/p/59bc62880039
本文僅介紹 javascript
語言中的正則,其他語言雖有不同,但很類似,感興趣的自行google吧。另外本文針對所有示例都給了輸出結果,希望讀者思考為什么返回這樣的結果,和自己期望的有什么不一樣,建議自己也動手輸入一下,加深記憶。
本文總體分兩部分:基礎知識 和 案例分析
第一部分 基礎知識
一、正則申明方式
1、構造函數方式
var reg = new RegExp('\d', 'gi');
- 通過
new
構造一個正則表達式對象,其中第一個參數'\d'
是正則內容,第二個參數'gi'
是修飾符。兩個參數皆為字符串類型 - 修飾符的作用是對匹配過程進行限定
- 修飾符有三種:i, g, m,可以同時出現,沒有順序(即
gi
與ig
一樣),請參考下方說明
修飾符 | 說明 |
---|---|
i | 忽略大小寫匹配 |
g | 全局匹配,即是匹配一個后繼續匹配,直到結束 |
m | 多行匹配,即是遇到換行后不停止匹配,直到結束 |
說明:
-
[a-z]
表示從小寫字母a到小寫字母z之間的任意字符(含a和z),下文會有詳細說明 -
+
表示至少出現一次 -
\n
在js中表示換行 -
^[a-z]
表示以任意小寫字母開頭的行
'aBcd efg'.match(/[a-z]+/);
// ["a"]
'aBcd efg'.match(/[a-z]+/i);
// ["aBcd"]
'aBcd efg'.match(/[a-z]+/g);
// ["a", "cd", "efg"]
'aBcd efg'.match(/[a-z]+/gi);
// ["aBcd", "efg"]
'aB\ncd\n efg'.match(/^[a-z]+/m);
// ["a"]
'aB\ncd\n efg'.match(/^[a-z]+/g);
// ["a"]
'aB\ncd\n efg'.match(/^[a-z]+/gm);
// ["a", "cd"]
// 注意不是 ["a", "cd", "efg"]
具體用法請看下文相關的示例
2、字面量方式
相比較上一種方式,這一種更為常見,上面示例也都使用了這種方式
var reg = /\d/gi;
兩個斜線內為正則的內容,后面可以跟修飾符,與第一種構造函數方式相比更簡潔,缺點是正則內容不能拼接,對于大多數場景倆說足夠了
二、正則相關符號
1、方括號 []
用法
用于查找方括號內的任意字符:
表達式 | 說明 | 示例 | 返回結果 |
---|---|---|---|
[abc] | 匹配方括號內的任意字符 | 'adobe'.match(/[abc]/g) |
["a", "b"] |
[^abc] | 匹配不在方括號內的任意字符 | 'adobe'.match(/[^abc]/g) |
["d", "o", "e"] |
[0-9] | 匹配任何從 0 至 9 的數字 | '2016s'.match(/[0-9]/g) |
["2", "0", "1", "6"] |
[a-z] | 匹配任何從小寫 a 至 z 的字符 | 'adobe 2016'.match(/[a-z]/g) |
["a", "d", "o", "b", "e"] |
[A-Z] | 匹配任何從大寫 A 至 Z 的字符 | 'adobe PS 2016'.match(/[A-Z]/g) |
["P", "S"] |
注意:
1)^
在 []
內開始位置及正則雙斜線開始位置有特殊含義,其他位置表示 ^
字符本身
-
//
正則開頭位置表示以某某開頭的字符串,如下表示以大寫或小寫字母開頭的且連續為字母的字符串:
'adobe 2016'.match(/^[a-zA-Z]+/);
// ["adobe"]
- 在正則 或 匹配中(即
|
匹配),表示 或者以某某字符開始的字符串,如下表示匹配 連續數字 或 以小寫字母開頭且連續為小寫字母的字符串,所以返回結果包含2016
和adobe
,注意返回結果不是["2016", "adobe"]
'adobe2016ps'.match(/\d+|^[a-z]+/g);
// ["adobe", "2016"]
- 在
[]
內開始位置時,表示不匹配[]
內除^
以外的所有字符:
'adobe'.match(/[^abc]/g);
// ["d", "o", "e"]
注: $
與 ^
的前兩個用法相似,只不過匹配的是以某某字符結尾的字符串,舉例:
'adobe 2016'.match(/\d+|[a-z]+$/g);
// ["2016"]
'adobe'.match(/\d+|[a-z]+$/g);
// ["adobe"]
2)-
(連字符)表示左邊字符的 ASCII 值到右邊字符 ASCII 編碼值之間及左右字符自身的所有字符
'adobe PS 2016'.match(/[a-g]/g);
// ["a", "d", "b", "e"]
3)-
連字符左側的字符對應的 ASCII 值一定要小于或等于右側的字符,否則會報語法錯誤
'adobe'.match(/[z-a]/);
// Uncaught SyntaxError: Invalid regular expression: /[z-a]/: Range out of order in character class...
4)如果希望對連字符 -
本身進行匹配,需要用反斜線轉義
'adobe-2016'.match(/[a-g\-]/g);
// ["a", "d", "b", "e", "-"]
5)查看 ASCII 表就會發現,大寫字母的 ASCII 值是小于小寫字母的,因此下面用法會報語法錯誤
'adobe-2016'.match(/[a-Z]/g);
// Uncaught SyntaxError: Invalid regular expression: /[a-Z]/: Range out of order in character ...
那么問題來了,如果要表示所有字母,不區分大小寫怎么辦呢?其實有兩種方式:
A、第一種是使用修飾符 i
,前面提到過。舉例:
'adobe-PS'.match(/[a-z]/gi);
// ["a", "d", "o", "b", "e", "P", "S"]
B、第二種是在正則中明確指明大小寫字母,舉例:
'adobe-PS'.match(/[a-zA-Z]/g);
// ["a", "d", "o", "b", "e", "P", "S"]
返回結果跟第一種一樣。當然這個例子有些特殊:匹配了所有大小寫字母。當只匹配部分大小寫字母的時候只能使用第二種方式,在此就不做示例了,讀者可以自己測試
6)匹配大小字母不能寫成 [A-z],雖然不會報語法錯誤,但隱式的放大了匹配范圍,查看 ASCII 會發現,在大寫字母 Z
到小寫字母 a
之間還有 [
、 \
、 ]
、 ^
、 _
、 ` 這6個字符,因此不能這么寫。
7)想必有同學會問, \w
不也可以匹配字母么?是的,\w
確實可以匹配字母,但跟上面說的一樣,也隱式的放大了匹配范圍,\w
除了匹配大小字母外還匹配了數字和下劃線,即 \w
與 [A-Za-z0-9_]
等價,當然 A-Z
、a-z
、0-9
(等價于\d
)、_
這四組沒順序之分
2、特殊含義字符
.
匹配任意單個字符,除換行和結束符
'1+0.2*2=1.4'.match(/.{2}/g);
// ["1+", "0.", "2*", "2=", "1."]
\w
匹配任意單詞字符(數字、字母、下劃線),等價于[A-Za-z0-9_]
'ad34~!@$ps'.match(/\w/g);
// ["a", "d", "3", "4", "p", "s"]
\W
匹配任意單詞字符,與\w
相反,等價于[^A-Za-z0-9_]
'ad34~!@$ps'.match(/\W/g);
// ["~", "!", "@", "$"]
\d
匹配數字,等價于[0-9]
'ps6'.match(/\d/g);
// ["6"]
\D
匹配非數字,等價于[0-9]
'ps6'.match(/\D/g);
// ["p", "s"]
\s
匹配空白字符,主要有(\n
、\f
、\r
、\t
、\v
),注意'a\sb'
中的\s
依然是字符s
,所以'a\sb'.match(/\s/g)
返回null
'adobe ps'.match(/\s/g);
// [" "]
\S
匹配非空白字符,與\s
相反
'adobe ps'.match(/\S/g);
// ["a", "d", "o", "b", "e", "p", "s"]
\b
匹配單詞邊界,注意連續的數字、字母或下劃線組成的字符串會認為一個單詞
'adobe(2016) ps6.4'.match(/\b(\w+)/g);
// ["adobe", "2016", "ps6", "4"]
\B
匹配非單詞邊界,仔細體會下面的示例與\b
的結果
'adobe(2016) ps6.4'.match(/\B(\w+)/g);
// ["dobe", "016", "s6"]
\0
匹配NUL字符
'\0'.match(/\0/);
// ["NUL"]
\n
匹配換行符(編碼:10,newline
)
'adobe\nps'.match(/\n/).index;
// 5
\f
匹配換頁符
'adobe\fps'.match(/\f/).index;
// 5
\r
匹配回車符(編碼:13,return
)
'adobe\rps'.match(/\r/).index;
// 5
\t
匹配制表符,鍵盤tab
對應的字符
'adobe\tps'.match(/\t/).index;
// 5
\v
匹配垂直制表符
'adobe\vps'.match(/\v/).index;
// 5
\xxx
匹配以八進制數xxx
規定的字符
'a'.charCodeAt(0).toString(8);
// "141"
'adobe ps'.match(/\141/g);
// ["a"]
\xdd
匹配以十六進制數dd
規定的字符
'a'.charCodeAt(0).toString(16);
// "61"
'adobe ps'.match(/\x61/g);
// ["a"]
\uxxxx
匹配以十六進制數xxxx
規定的Unicode
字符,注意位數不夠需要補0
'a'.charCodeAt(0).toString(16);
// "61"
'adobe ps'.match(/\u0061/g);
// ["a"]
注意:
window系統回車換行符為
\r\n
,linux系統下沒有\r
,linux系統通過vi
編輯器打開window系統的文本文件時候,經常在行尾出現^M
符號,也就是\r
的原因,解析文本的時候需要注意相關判斷。
3、量詞說明
n+
匹配包含至少一個n的字符串
'adobe paas'.match(/a+\w+/g);
// ["adobe", "aas"]
n*
匹配包含零個或多個n的字符串
'ab3 aa12bb'.match(/a*\d+/g);
// ["3", "aa12"]
n?
匹配包含零個或一個n的字符串
'ab3 aa12bb'.match(/a?\d+/g);
// ["3", "a12"]
n{x}
匹配包含連續x
個n的字符串
'ab3 aa12bb aaa34'.match(/a{2}\d+/g);
// ["aa12", "aa34"]
n{x,y}
匹配包含至少連續x
個且最多連續y
個n的字符串
'a3 aaa12bb aaaaaaa34'.match(/a{2,4}\d+/g);
// ["aaa12", "aaaa34"]
n{x,}
匹配包含至少連續x
個n的字符串
'a3 aaa12bbaa4'.match(/a{2,}\d+/g);
// ["aaa12", "aa4"]
由上可知,以下 表達式1 與 表達式2 等價
表達式1 | 表達式2 |
---|---|
n+ |
n{1,} |
n* |
n{0,} |
n? |
n{0,1} |
4、符號說明
符號 {}
、^
、$
、*
、+
、?
、[]
、[^]
、-
已經在前面介紹過,接下來看下其他特殊字符
a|b
匹配包含a
或b
的字符串
'adobe ps13'.match(/([a-g]+l\d+)/g);
// ["ad", "be", "13"]
/
字面量方式申明正則時的界定符
'adobe'.match(/\w+/);
// ["adobe"]
\
普通反斜線字符
'a\\dobe'.match(/\\/);
// ["\"]
5、小括號 ()
用法
正則在非全局(g
)模式下,通過match方式,返回的數組第一個值整體匹配的字符串,其他值為通過括號分組匹配到的
1)捕獲用法
- 表示對匹配的字符串進行分組
'adobe cs9cs10, adobe cs11'.match(/([a-z]+\d+)+/);
// ["cs9cs10", "cs10"]
// 注意{2,}是對 括弧內的匹配 的描述
- 與
|
一起使用表示選擇性
"he is 12. she is 13. it's box".match(/(it|she|he)\s+is/g);
// ["he is", "she is"]
- 表示對匹配的字符串捕獲
'adobe cs9'.match(/[a-z]+\d+/);
// ["cs9"]
'adobe cs9'.match(/[a-z]+(\d+)/);
// ["cs9", "9"]
- 表示對匹配的字符串反向引用,引用從
\1
開始,從正則左側第一個左括號(當然要是閉合的括號才行)開始計算,每多一對括號,引用數加一,在非捕獲情況下不會加一。但正則比較復雜時,減少引用可以提升匹配性能,關于 非捕獲 下方會詳細介紹
引用的結果可以通過 構造函數 RegExp
獲取,即 RegExp.$1
一直到 RegExp.$9
'Can you can a can as a canner can can a can?'.match(/([cC]an+)\s+\1/g);
// ["can can"]
// 注意 `\1` 等價于正則里的 `([a-z]+)`,即與下面示例相同
'Can you can a can as a canner can can a can?'.match(/[cC]an+\s+[cC]an+/g);
// ["can can"]
// 如果把括弧去掉可以看下結果
'Can you can a can as a canner can can a can?'.match(/[cC]an+\s+\1/g);
// null
2)非捕獲用法,以(?)
形式出現
(?:n )
表示非捕獲組
// 不使用括號時
'adobe12ps15test'.match(/[a-z]+\d+[a-z]+/);
// ["adobe12ps"]
// 使用括號分組
'adobe12ps15test'.match(/[a-z]+(\d+)([a-z]+)/);
// ["adobe12ps", "12", "ps"]
'adobe12ps15test'.match(/[a-z]+(?:\d+)([a-z]+)/);
// ["adobe12ps", "ps"]
// 看起來上面語句不用(?:)也可以得到相同結果,即:
'adobe12ps15test'.match(/[a-z]+\d+([a-z]+)/);
// ["adobe12ps", "ps"]
// 注意,但需求希望匹配字母之間的規則復雜時,如希望匹配字母,且字母之間可以為1或3時,但不需要1和3
'adobe11ps15test'.match(/[a-z]+(1|3)+([a-z]+)/);
// ["adobe11ps", "1", "ps"]
// 返回中不希望包含數字怎么辦,可以使用非捕獲
'adobe11ps15test'.match(/[a-z]+(?:1|3)+([a-z]+)/);
// ["adobe11ps", "ps"]
(?=n )
匹配任何其后緊跟字符n的字符串,但返回中不包含n
'adobe12ps15test'.match(/[a-z]+(?=\d)/g);
// ["adobe", "ps"]
(?!n )
匹配任何其后沒有緊跟字符n的字符串,返回中不包含n
'adobe12ps15test'.match(/[a-z]+(?!\d)/g);
// ["adob", "p", "test"]
(?<=n )
匹配任何其前緊跟字符n的字符串,返回中不包含n
'adobe12ps15test'.match(/(?<=\d)[a-z]+/g);
// ["ps", "test"]
(?<!n )
匹配任何其前緊跟字符n的字符串,返回中不包含n
'adobe12ps15test'.match(/(?<!\d)[a-z]+/g);
// ["adobe", "s", "est"]
3)注意
- A、如果希望對上面特殊字符本身進行匹配,需要在其前面添加
\
進行轉移
'11+2=13'.match(/\d+\+/g);
// ["11+"]
'(11+2)*2=26'.match(/\(\d+\+\d+\)/g);
// ["(11+2)"]
- B、
\\
舉例
// 注意下面兩個表達式返回的結果
'path C:\Windows\System32'.match(/([a-zA-Z]:\\\w+)/g);
// null
'path C:\\Windows\\System32'.match(/([a-zA-Z]:\\\w+)/g);
// ["C:\Windows"]
說明:
在申明字符串 'path C:\Windows\System32' 時,其中的 '' 就已經被當做轉移符,既是
'\W' === 'W'
,所以如果希望申明的字符串中包含反斜線,需要在加一個反斜線轉義,即\\
6、正則相關方法
1) RegExp對象相關方法
方法名 | 使用場景 | 返回值 | 示例 |
---|---|---|---|
test | 判斷是否匹配 | true/false | /\d/.test('2016s') |
exec | 返回匹配的結果,與match類似 | 數組或null | /\d/.exec('2016s') |
2)String對象相關方法
方法名 | 使用場景 | 返回值 | 示例 |
---|---|---|---|
match | 返回匹配的結果,非全局條件下與exec返回結果一致,并擁有指向匹配字符串的信息,全局條件下一次性返回所有匹配的結果 | 數組或null | '2016s'.match(/\d+/) |
replace | 將字符串替換成另外的字符串或將正則的匹配字符串替換成其他子串 | 字符串 | '2016s'.replace(/\d+/, '123') |
search | 查找第一次匹配子串的位置,返回index值,否則返回-1 | index | '2016s'.search(/s/) |
split | 按約定字符串或字符串規則拆分成數組,接受一個字符串或正則 | 數組 | '20,1,6s'.split(/,/) |
3)replace
具體用法
顧名思義,是字符串替換方法,但用法比較廣泛,相信讀者已經非常熟悉了。在此就當復習了
A、 基本用法
直接傳入字符串進行替換,找到子串后只替換一次,舉例:
'adobe abc'.replace('b', '_')
// "ado_e abc"
// 注意 第二個 b 沒有被替換
如果希望全部替換,可以使用正則表達式并用全局修飾符 g
方式,舉例:
'adobe abc'.replace(/b/g, '_')
// "ado_e a_c"
B、 高級用法
第二個參數可以使用 function
,其中有三個參數,分別為 匹配的字符串、當前匹配的字符串index值、匹配的源字符串,最終結果根據每次匹配結果進行相應的替換
舉例:
'adobe aacc bbaa'.replace(/a+/g, function(str, index, source){
if(index > 0){
return str.toUpperCase();
} else {
return str;
}
});
// "adobe AAcc bbAA"
第二部分 案例分析
一、常見匹配
在寫正則之前,需要注意以下幾點:
- 一定要清楚期望的規則是什么,不然無從匹配
- 有些正則不只一種寫法,要注意簡短干練,復雜的正則表達式不僅難懂,而且容易出BUG,性能也不是很好
- 正則雖好,可要適度奧。有些字符串處理不一定適合用正則
1、手機號
規則:以1開頭第二位為3
、5
、7
、8
且長度為11位的數字組合
/^1[3578]\d{9}$/.test(13600001111);
// true
2、 字符串提取
舉例:提取字符串中的數字
分析:
根據對數字的理解,可能為負數,即
-?
,如果是負數,其后需要是數字且至少一位,即-?\d+
,小數部分可能有也可能沒有,所以需要對小數部分括弧起來用?
或{0, 1}
限定,因為.
是特殊字符需要轉義,于是表達式為:-?\d+(\.\d+)?
'(12.3 - 32.3)*2 = -40'.match(/-?\d+(\.\d+)?/g);
// ["12.3", "32.3", "2", "-40"]
二、jQuery中的正則片段
1、表達式
在jQuery 3.1.2-pre中找到一個解析單標簽的正則,如下:
/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i
2、分解
乍一看有點懵,其實拆解之后就容易理解了,注意拆解的步驟,通常來說:
1) 第一步可以先看括號 ()
,可以將各個小括號及非括號的分成不同部分,如
/^< ([a-z][^\/\0>:\x20\t\r\n\f]*) [\x20\t\r\n\f]*\/?> (?:<\/\1>|) $/i
2) 第二步可以將中括號分開
/^< ( [a-z] [^\/\0>:\x20\t\r\n\f]* ) [\x20\t\r\n\f]* \/?> (?:<\/\1>|) $/i
現在是不是已經很清楚了,接下來分解下,就很容易理解了
3、詳解
1)
^<
很明顯在匹配標簽左尖括號括號,且以其開始2)
( [a-z] [^\/\0>:\x20\t\r\n\f]* )
這個括號有兩部分,第一個[a-z]
沒什么好解釋,即標簽<
緊跟的必須為字母,因為全局加了i
(忽略大小寫) 修飾符,所以大小寫字母都可以;[^\/\0>:\x20\t\r\n\f]*
,及限制標簽名必須以字母開始,且第二個字母不能為/
\0
>
:
\20
t
\r
\n
\f
的任意多個字符(思考為什么),()
表示對標簽的分組,方便取到標簽名3)
[\x20\t\r\n\f]*
表示可能含有[\x20\t\r\n\f]
這些特殊字符,與前面的[^\/\0>:\x20\t\r\n\f]*
相似卻不一樣,通過這里可以看出<br
之后進行回車也能匹配到4)
\/?>
能匹配<br>
或<br/>
5)
(?:<\/\1>|)
這里不捕獲,并用\1
去反向引用第一個括號的表達式([a-z][^\/\0>:\x20\t\r\n\f]*)
。這里的|
表示<\/\1>
可有可無,即:(?:<\/\1>|)
與(?:<\/\1>)?
匹配結果一樣
參考資料
[1] https://en.wikipedia.org/wiki/Regular_expression
[2] 解惑正則表達式中的捕獲