這些是本人在 github.pages 上寫的博客,歡迎你們關注和糾錯,本人會按期在github pages 上更新。有想要深刻了解的知識點能夠留言。jquery
同時,這是本人第一次寫文章,若有目錄結構不合理,還請指出。ios
剛開始學習 JS 時,正則表達式一直是我不肯意麪對的,每次讀到有關正則表達式的時候,都會避而遠之。但是,一次,當我打開 JQ 源碼的時候,發現裏面有大量的正則表達式。因而乎,本身就強迫本身學習正則,學習的過程仍是蠻愉快的。最後,真香定律終於出現了。哈哈哈!!git
這篇教程我會由淺入深的來和你們分享正則表達式,讓你們即學習到正則表達式的用法,也瞭解其在 JS 中表現的鮮爲人知的一面。es6
正則表達式是 JS 中很重要的一環。也是對不少人比較不肯意麪對的一個知識點。可是,當咱們真正掌握了正則表達式,能夠利用其在咱們的代碼中發揮很大的威力,大大的簡化咱們的代碼。對於喜歡閱讀一些庫源碼的夥伴。這個真的是必須掌握的。github
固然,正則基本在每一個語言都有實現。雖不能說都相同。可是基本上都是大同小異。此外,正則表達式的範圍很是的廣,這裏也不可能每一個知識點都會涉及到。這裏,做者會將一些我認爲常見的,重要的,常見的注意點給你們一一分析。正則表達式
正則表達式,英文叫作 Regular Expression。按照英文字面意思解釋,就是有規則的表達式,正則表達式就是由一些列的語法規則組成的字符串,而後按照這種組合的規則去匹配一些字符串,篩選出符合條件的字符串。數據庫
知識瞭解:根據 ECMA5 規範,JS 中正則表達式的格式和功能是以 perl 語言爲藍本的。express
咱們平時寫的正則表達式很不直觀,這裏推薦一個在線工具。該工具以可視化的界面來描述咱們寫的正則表達式。工具比較簡單,你們自行了解。 在線工具編程
正則表達式的表示方法有兩種bash
let reg = /text/ig
let regExp = new RegExp('text', ig)
複製代碼
兩種表示方法均可行。區別在於:利用構造函數進行表示的時候,能夠動態的構建正則表達式的規則。
let str = String('****')
// 如 let className = str + 'name'
regExp = new RegExp(className, 'ig')
複製代碼
正則表達式的組合規則太多了,你們能夠去看一下 ECMA 規範。下面咱們就介紹一些經常使用的,常常遇到的狀況。
正則表達式的組成通常由如下幾類構成
在 JavaScript 中,全局修飾詞有 g、i、m、y、u、s
這些修飾詞在正則表達式的匹配中,起到了很重要的做用。
g: 表明全局匹配,當匹配到目標字符串的時候,不會中止匹配,而會繼續匹配。直到匹配結束爲止。
i: 匹配的過程當中,忽略大小寫
m: 換行匹配。字符串能夠換行,若是當前行沒有匹配到,能夠換行繼續匹配
y: 執行「粘性」搜索,匹配從目標字符串的當前位置開始
u: 至關於將匹配模式轉換成 unicode 模式,能夠正確處理大於\uFFFF的 Unicode 字符。你們能夠自行參考 瞭解
s: 咱們知道 元字符 . 表明除了回車換行符之外的全部字符,可是加上 s 修飾符後, . 能夠匹配任意字符
/./.text('\n') // false
/./s.test('\n') // true
//其實還有另外一種技巧能夠匹配全部字符
/[^]/.test('\n') // true
複製代碼
y 表明什麼含義? 這個等會再作解釋,先簡單說下,這個與正則表達式的一個屬性 lastlndex 有關係,如今解釋,可能會一臉懵逼。 下面會與 g 標誌 一塊兒討論。
元字符是在正則表達式中有特殊含義的非字母字符。這些元字符使得正則表達式的組合規則十分強大。
// 引用自規範原文 咱們能夠看一下這些元字符都有哪些,咱們應該很熟悉這其中表明的含義。
PatternCharacter :: SourceCharacter but not any of:
^ $ \ . * + ? ( ) [ ] { } |
此外,還有一些字符,是組合而成的,表明特殊的含義
有 \b、\B、\d、\D、\w、\W、\s、\S、\f、\v、\t、\n、\r 等等。這些字符表明的含義,你們自行了解,這裏不一一講解這些元字符的含義。
下面舉例子會用到一些,會對其含義作說明。
複製代碼
既然元字符表明這麼多的含義,那麼咱們若是須要在字符串中匹配這些字符怎麼辦呢? 這個時候,可使用轉義字符幫忙。
舉個栗子:
reg = /\d/ // \d 是元字符,表示數字,這個正則表達式只能匹配數字,若是咱們須要匹配'\d'呢
reg.test('\d') // false
這時候須要用轉義字符轉義
reg = /\\d/ // \ 表明轉義字符
reg.test('\d') // 仍是false
這個爲何仍是 false 呢,不是按照規矩辦事嗎?
這是由於 JS 裏的字符串也有轉義字符!
能夠試一下 '\d'.length 等於1,這個時候要想匹配 '\d' 必需要在字符串中對其轉義
reg.test('\\d') // true
複製代碼
總結:遇到這種狀況,別老是忙着爲正則表達式轉義,還得爲字符串轉義,關於字符串裏面的轉義字符,這超過了本篇討論範圍,你們自行了解。
下面介紹一些經常使用的元字符
元字符 | 表示及含義 | 解釋 |
---|---|---|
. | /[ ^\r\n ]/ | 除了回車換行之外的所有字符 |
\d | /[0-9]/ | 數字字符 |
\D | /[^0-9]/ | 非數字字符 |
\w | /[0-9a-zA-Z_]/ | 單詞字符(數字,字母,下劃線) |
\W | /[^0-9a-zA-Z_]/ | 表明非單詞字符 |
\s | /[\f\n\r\t\v\u00a0\u1680\u180e \u2000-\u200a\u2028\u2029\u202f \u205f\u3000\ufeff]/ |
空白字符,包括空格、 製表符、換頁符和換行符 |
\S | /[^\s]/ | 非空白字符 |
| | /x|y/ | x or y |
\b | word boundary | 單詞邊界 |
\B | none word boundary | 非單詞邊界 |
^ | /^abc/ | 匹配以abc爲開始的字符串 |
$ | /abc$/ | 匹配以abc爲結束的字符串 |
咱們都知道用特定的正則表達式去匹配特定的字符串很輕鬆。由於不會出現其餘狀況,邏輯上講是很是清晰的事情。
舉個栗子:
let reg = /abc\b/
// 以下圖 表示匹配 abc後面跟上單詞邊界。
reg.test('abc bcd') // true
reg.test('abcc') //false 由於abc後面緊跟單詞邊界
複製代碼
但是,有時候咱們的需求不是匹配特定字符,而是要匹配符合一些特性的字符。好比,須要匹配 a b c 任意一個,存在即知足條件。
簡而言之:我只要大家中的一個出現就OK。
這個時候,咱們就可使用元字符 [] 來構建這樣一個 字符類。
這裏的類,咱們能夠聯想到編程語言的類,泛指一些符合特性的事物,而不是特指。
舉個例子:
let reg = /[abc]\b/
// 以下圖 表示 one of abc 後面緊跟單詞邊界就知足條件
reg.test('a') // true
複製代碼
字符類很強大,可是,若是咱們的需求是要匹配除了一個字符類以外的字符呢?
簡而言之:別人都行,就大家不能夠。
這個時候,咱們可使用字符類的反向類,使用元字符 ^ 在寫好的字符類裏面取反。
舉個例子:
let reg = /[^abc]\b/
// 以下圖 表示 None of abc 後面緊跟單詞邊界就知足條件
reg.test('e') // true
複製代碼
解釋一下單詞邊界的含義:單詞邊界這個概念,不少人都比較模糊。我只能說一下個人理解 在正則表達式中,\w 表明單詞字符,\W 表明非單詞字符,只要單詞字符緊挨着非單詞字符,那麼在這二者中間,就存在單詞邊界。
字符類給咱們注入了一種全新的功能,相似於數據庫的模糊查詢。咱們能夠利用這一功能匹配範圍內的字符串了。 可是,應用場景多了,也會出現問題。
舉個例子:
//咱們想要匹配 數字1到8中的任意一個,咱們利用字符類的概念能夠這樣寫
reg = /[12345678]/
// 可能有的小夥伴能夠接受,那好,若是咱們想要匹配小寫字母,a 到 z 的任意一個字符
reg = /[abcdefghijklmnopqrstuvwxyz]/ // 這樣一坨,寫的很難受
複製代碼
看上面的例子,寫代碼的難受,讀代碼的估計也不舒服。這時候,咱們須要範圍類幫忙。
所謂的範圍類,就是匹配具備必定規則的一段範圍以內的字符:
使用字符 -,來達成這一目標
舉個例子:
// 匹配 a 到 z 的任意一個字符
reg = /[a-z]/
// 匹配除了 a 到 z 的任意一個字符
reg = /[^a-z]/
// 常見的模式
/\d/ 就至關於 /[0-9]/
/\D/ 就至關於 /[^0-9]/
/\w/ 就至關於 /[a-zA-Z0-9]/
/\W/ 就至關於 /[^a-zA-Z0-9]/
複製代碼
一個問題:**-**不是元字符,是否能夠在範圍類中匹配?若是能夠,是否須要轉義或者其餘特殊操做。
匹配該字符在字符類中是能夠的,可是有注意點:即 - 只能夠放在範圍類的開頭或結尾,纔會匹配該字符。
不能夠出如今兩個字符中間,否則,該字符仍是會被看成範圍類中的特殊字符來對待
舉個例子:
let reg = /[1-z]/
reg.test('-') // false
reg.test('a') // true
let reg = /[1-9-]/
reg.test('-') // true
reg.test('1') // true
複製代碼
咱們以前介紹的字符類或非字符類,只能匹配特定類出現一次,若是出現屢次,須要額外再寫相同的代碼進行匹配。
舉個例子:
// 需求:匹配有連續5個數字的字符串
let reg = /\d\d\d\d\d/ // 那若是要匹配出現連續3至6個數字的字符串呢?
let reg = /\d\d\d|\d\d\d\d|\d\d\d\d\d|\d\d\d\d\d\d/ // 這樣寫太複雜,我須要更簡單的寫法
複製代碼
有這樣的需求時,咱們就須要量詞來幫忙。
量詞有幾種表示的方式,各自表明不一樣的需求。
量詞的表示有如下這幾種表示:
+ 表示匹配一次或一次以上。one or more
? 表示匹配0次或一次。 one or less
* 表示匹配0次或0次以上。none or onemore
{m,n} 表示匹配 m 到 n 次。[m,n]閉區間 m less n most
{m,} 表示匹配至少 m 次。 m less
{m} 表示匹配出現 m 次
若是要表示至多出現 m 次,能夠這樣表示 {0,m}
重寫例子:
reg = /\d{5}/ // 匹配有5個連續數字的字符串
reg = /\d{3,6}/ // 匹配有連續 3 至 6 個數字的字符串
複製代碼
你們能夠在圖形化工具裏面本身嘗試下,很直觀。
注意:這裏,咱們會先應用 String.prototype.replace 方法來很形象的解釋正則表達式的貪婪模式。
來看一下這樣等應用場景:咱們須要匹配連續 5-10 個小寫字母等場景。目標字符串知足這個需求,可是匹配等結果是什麼?
是匹配到5個字母就不匹配仍是繼續匹配更多等字母直到匹配失敗呢?
人是貪婪的,因此人設計的正則表達式也是貪婪的。
在正則表達式中,會盡量的匹配更多的字符,直到匹配失敗爲止。
舉個例子:
reg = /[a-z]{5,10}/
str = 'ahhsjkiosbsasdasllk' // str.length === 19
咱們用 replace 方法來驗證一下,正則表達式匹配了多少字符
str1 = str.replace(reg, 'Q') // str1.length === 10
str1 = 'Qsasdasllk'
複製代碼
上述的例子,咱們能夠看出,正則表達式是屬於貪婪模式。那麼咱們如今想要取消貪婪模式,能夠嗎?
能夠,只須要在量詞後加上**元字符?**就能夠取消貪婪模式啦。
舉個例子:
reg = /[a-z]{5,10}?/
str = 'ahhsjkiosbsasdasllk' // str.length === 19
咱們用 replace 方法來驗證一下,正則表達式匹配了多少字符
str1 = str.replace(reg, 'Q') // str1.length === 15
str1 = 'Qkiosbsasdasllk'
複製代碼
假如,咱們如今須要這樣一個需求,須要匹配包含'mistyyyy'連續重複2次的字符串。
這種狀況,咱們按照以前的寫法可能會這樣寫。
/mistyyyy{2}/
複製代碼
可是,這樣匹配是錯誤的,這表達的意思是y重複2次,而不是 mistyyyy 重複2次
這個時候,咱們可使用分組這個概念來幫助咱們。
用法:將須要分組的信息,用元字符()包含起來。這樣就可使量詞做用於分組了。
reg = /(mistyyyy){2}/
str = 'Im mistyyyymistyyyymistyyyy yeah'
reg.test(str) // true
以下圖所示
複製代碼
再看一個例子,這時候,我要更名字了。mistyyyy 或者 missyyyy 都是能夠的。那咱們怎麼匹配它呢?
//咱們能夠這樣寫
reg = /mistyyyy|missyyyy/
可是利用分組,咱們能夠這樣寫
reg = /mis(s|t)yyyy/
reg.test('mistyyyy') // true
reg.test('missyyyy') // true
複製代碼
如今咱們來看一個,平時開發中常常出現的需求。如:將日期 '2018-12-23' 轉換爲 '23/12/2018'
這個時候,咱們就很頭大了。單純的匹配到這個日期並不困難。可是如何將其轉換這就成了難點。固然,咱們能夠進行最原始的方法進行解決。
reg = /\d{4}-\d{2}-\d{2}/
'2018-12-23'.replace(reg, '23/12/2018')
// 這樣的程序基本沒有靈活性。
複製代碼
這時候,咱們要講的捕獲就要出現了。前面講到了分組,既然能夠分組,那咱們也能夠捕獲分組。
捕獲分組又能夠稱爲引用:
咱們先看正向引用,舉個最適合的例子。
//咱們如今須要匹配一個 dom 節點,好比匹配 id 爲 container 的 div dom 節點。
let domContainer =
`<div>
<div id="container">
this is container
</div>
</div>`
reg = /<div id="container">([^<\/]+)<\/div>/m
domContainer.replace(reg, 's') // <div>s</div>
複製代碼
這個時候,咱們可使用正向引用,即 \1 表明第一個分組的引用, \2 表明第二個分組的引用等等 以此類推
咱們來重寫正則表達式。
reg = /<(div) id="container">([^<\/]+)<\/\1>/m
// 你們能夠試一下,一樣的效果。
複製代碼
介紹完正向引用,咱們來看一下反向引用。
在正則表達式進行分組時,當匹配結束時候,咱們但願能夠以分組爲單位進行字符串的替換,這樣可行嗎?
舉個例子
reg = /(\d{4})-(\d{2})-(\d{2})/
// 這樣咱們就把正則寫好了。考慮到以前的例子,咱們須要將第三個分組放在開頭,第二個分組位置不變,第一個分組放在最後
// 如何作
'2018-12-23'.replace(reg, '$3/$2/$1') // "23/12/2018"
複製代碼
由上述例子能夠得知,反向引用就是用 '$1' 獲取第一個分組 '$2' 獲取第二個分組...以此類推
注意;是 '$1' 表明一個分組,而不是 $1,這裏須要注意一下
有時候,咱們根本不須要捕獲一個分組,就如剛纔 reg = /mis(s|t)yyyy/ 一種狀況。咱們只是想用分組實現一下 或 操做。 沒有分組的必要,其次,當正則表達式變得複雜起來,保持明顯的分組是頗有必要的。
這個時候,咱們能夠在分組的括號裏面加上 ?: 這樣就能夠取消捕獲了
reg = /mis(s|t)yyyy/
'mistyyyy'.replace(reg, '$1') // 't'
// 取消捕獲
reg = /mis(?:s|t)yyyy/
'mistyyyy'.replace(reg, '$1') // '$1'
複製代碼
咱們能夠看下圖片的比較,沒有分組了。說明取消了捕獲。
先解釋這兩個概念,咱們都知道在 JavaSCript 中,正則表達式匹配的順序是順着目標字符串進行匹配。
若是咱們須要設計一些帶條件的匹配規則,好比說:咱們須要匹配字符串 'mistyyy' 後面必須是 'good'
舉個例子:
reg = /mistyyyygood/
複製代碼
這個時候,'mistyyyy' 後面是 'good' 可是此時,'good' 也被匹配到了,若是咱們用 replace 作替換,那麼 good 也會被替換掉。
要知足這樣的條件。咱們可使用正向匹配
規則以下:
舉個例子;
str = 'mistyyyygood boy'
/mistyyyy(?=good)/.test(str) // true
str.replace(/mistyyyy(?=good)/, 'you') // yougood boy
/mistyyyy(?!good)/.test(str) // false
複製代碼
注意:此時,condition 只是做爲條件進行篩選,並不會被匹配到。
咱們能夠看一個例子
str = 'mistyyyygood boy'
str.replace(/mistyyyy(?=good)/, '$1') // $1good boy
// 咱們能夠看出條件是不會被匹配到到。
複製代碼
反向匹配,與正向匹配的規則相反,該特性是 ES 2018 新加的特性。
規則以下:
咱們隨便寫一個正則,看一下打印出來的正則表達式的屬性有哪些
reg = /\u0002/yimgus
{
dotAll: true,
flags: 'gimsuy',
global: true,
ignoreCase: true,
lastIndex: 0,
multiline: true,
source: '\u0002',
sticky: true,
unicode: true,
__proto__: Object
}
複製代碼
dotAll,global,ignoreCase,multiline,stricky,unicode 分別表明修飾詞 s,g,i,m,y,u 是否出如今正在表達式的修飾詞位置。
flags 表示出現的修飾詞。
source 表示正則表達式的規則主體部分。
lastIndex 我的認爲最重要的屬性就是該屬性。下面會圍繞該屬性展開拓展一下。
咱們先看個奇怪的例子:
reg = /\d{2}/g
str = '12sd'
reg.lastIndex // 0
reg.test(str) // true
reg.lastindex // 2
reg.test(str) // false
reg.lastindex // 0
複製代碼
lastIndex的值類型是 number 類型。能夠進行讀寫操做。
舉個例子:
reg = /\d{2}/
reg.lastindex // 0
reg.lastIndex = 2
reg.lastindex // 2
複製代碼
該屬性的含義是從目標字符串的 lastIndex 位置開始進行匹配。可是這是有限制的,只有當修飾符存在 g 或者 y 的時候,纔會起做用。
舉個例子:
reg = /\d{2}/
str = '12sd'
reg.lastIndex = 2
reg.test(str) // true
reg.lastIndex // 2
reg =/\d.\d/s
str = '1\n2'
reg.lastIndex = 2
reg.test(str) // true
reg.lastIndex // 2
reg = /\d{2}/g
str = '12sd'
reg.lastIndex = 2
reg.test(str) // false
reg.lastIndex // 0
reg = /\d{2}/y
str = '12sd'
reg.lastIndex = 2
reg.test(str) // false
reg.lastIndex // 0
複製代碼
經過以上的例子,咱們能夠看出,lastIndex 屬性隻影響了 修飾符 g 和 修飾符 y 的匹配結果。
也就是說:只有這兩種的形式是從目標字符串的 lastIndex 位置進行匹配的,其餘的修飾符會忽略這個屬性。
並且,這兩種修飾符匹配失敗了,lastIndex 會重置爲0。
因而可知,g 修飾符 和 修飾符 y 有相同的做用。那麼咱們來探尋一下他們的異同點。
相同點:
reg1 = /\d/g
reg2 = /\d/y
str = '1ssss'
reg1.lastIndex = reg2.lastIndex = 1
reg1.test(str) // false
reg2.test(str) // false
reg1.lastindex // 0
reg2.lastindex // 0
// 說明都受 lastIndex 的影響,且匹配失敗都會置爲0
reg1.test(str) // true
reg2.test(str) // true
reg1.lastIndex // 1
reg2.lastIndex // 1
// 匹配成功後,lastIndex 都會重置爲匹配成功的字符串(chharAt(0))的下一個字符
reg1 = /^\d/g
reg2 = /^\d/y
str = '1ssss1'
reg1.lastIndex = reg2.lastIndex = 1
reg1.test(str) // false
reg2.test(str) // false
複製代碼
不一樣點:
修飾符 g 是全局匹配,即匹配到目標字符串不會中止,繼續匹配下去,直到沒有找到全部符合規則的爲止。修飾符 y 不是全局匹配,找到符合規則的就會中止。
舉個例子:
reg1 = /\d/g
reg2 = /\d/y
str = '1sssss1'
reg1.test(str) // true
reg1.lastIndex // 1
reg1.test(str) // true
reg1.lastIndex // 7
reg2.test(str) // true
reg2.lastindex // 1
reg2.test(str) // false
reg2.lastindex // 0
// 說明 修飾符 g 是全局匹配,而 y 不是全局匹配。
複製代碼
關於正則表達式,咱們講了一些常見的語法和一些比較生澀的疑難點。對於正則表達式,咱們掌握了這些知識點,並不能徹底發揮其應有的實力。
咱們還應該掌握,應用正則的一些方法有:String 類型的 split,replace,match,search 。RegExp 類型的 test,exec 方法。
真正瞭解這些方法的應用,才能讓正則表達式的強大威力。這些方法,這裏暫時不講了,小夥伴們應該熟悉這些方法的使用,利用他們組合正則表達式,展現出強大的威力。
此外,正則表達式常見的應用場景有模版的解析,dom節點的提取分離,這裏面含有大量複雜的正則表達式。若是小夥伴須要精通掌握正則表達式,能夠閱讀sizzle這個庫,該庫是 JQ 的核心部分,專門處理複雜的 dom selector,但願咱們能夠繼續努力,將正則表達式真正的掌握。這樣,咱們就能夠寫出更加優雅,更加具備可讀性的代碼了。