多是最好的正則表達式的教程筆記了吧...

筆記是由油管的@The Coding Train老師發佈系列教程。 由於正則我本身看了不少次,可是很快又忘記。因此爲了完全搞懂,一邊學習一邊記筆記,以給別人講課的方式記筆記,我本身的印象會更深,因此就有了如下內容。 小白的晉級路在我的github會持續更新哦: 傳送門,歡迎starjavascript

秋招&面試系列:html

1.VK的秋招前端奇遇記(一)前端

2.VK的秋招前端奇遇記(二)java

3.VK的秋招前端奇遇記(三)git

4.VK的秋招前端奇遇記(四)github

5.番外篇:前端面試&筆試算法 Algorithm面試


正則表達式

1.1. 基本語法

經過一張圖表來對正則表達式的基本進行一個回顧正則表達式

single char quantifiers(數量) position(位置)
\d 匹配數字 * 0個或者更多 ^一行的開頭
\w 匹配word(數字、字母) + 1個或更多,至少1個 $一行的結尾
\W 匹配word(數字、字母) ? 0個或1個,一個Optional \b 單詞"結界"(word bounds)
\s 匹配white space(包括空格、tab等) {min,max}出現次數在一個範圍內
\S 匹配white space(包括空格、tab等) {n}匹配出現n次的
. 匹配任何,任何的字符

1.1.1. single char

假設你有一段字符以下: 算法

字符

  • \w

將匹配全部word,固然,() - 等字符除外數組

  • \w\w\w

發現匹配的有'These are some phone numbers ...' 注意正則表達式是匹配一個連續串的規則,因此能夠看到三個字母的單詞能夠匹配到,6個單詞的也能夠匹配到。

  • \s\s

匹配到一行中連續兩個空格

quantifiers

假設咱們有這一段話:

The colors of the rainbow have many colours 
and the rainbow does not  have a single colour.
複製代碼

咱們想把全部的顏色找出來colors colours colour

答案 colou?rs? 嗯,看起來很簡單,很方便。


好了,如今想要匹配一行中的4個數字,或者一行中的5個字母等,這時候用quantifiers就很是方便了。

我如今想找5個字母組成的單詞

  • \w{5} 這樣能夠嗎?嗯..不行的,看下它匹配的內容,以下: 'These are some phone numbers 915-555-1234...' 的確,咱們模板給的很簡單,它只找一行中,連續出現5個字母的序列。因此如今改進一下好了

  • \w{5}\s 爲了能找到單詞,因此我但願5個字母后,跟一個空格的序列,這樣應該能夠了吧,看下匹配狀況: 'Theseare some phonenumbers915-555-1234...' 嗯,是的,只有目前這些方法,是作不到的。 因此,咱們須要第三個工具 "position"

1.1.2. position

回到剛纔的問題以前,先熟悉下^ $\b

This is somthing
is about
a blah
words
sequence of words
Hello and
GoodBye and 
Go gogo!
複製代碼

來看下各類正則所匹配的內容

  • \w+ 這個應該毫無疑問,匹配全部的words

  • ^\w+ 多了一個^,這樣子,就只能匹配到每一行開頭的單詞了This is a words sequence Hello GoodBye Go

  • \w+$ 這樣就能匹配到每行的最後一個字母

回到剛纔的問題

如今想找5個字母組成的單詞

就變得很簡單了,使用單詞結界符\b

答案就是\b\w{5}\b

1.1.3. 找個電話號碼吧

最後,找一個剛纔出現的電話號123-456-1231

用以上最基本的正則方法就是 \d{3}-\d{3}-\d{4},這樣就找到了。 可是有的時候,電話號碼是123.456.1234 或者 (212)867-4233的結構怎麼辦呢?

正則表達式中的或者其餘表達方式,下面一一來介紹。

1.2. 字符分類(char class)

前面記錄了最基本的方法,接下來講一下分類符[]

這個符號用來表示邏輯關係,好比[abc]表示a或者b或c.[-.]表示符號-或者.號(注意這裏,在[]中的.號表明的就是這個符號,可是若是在其外面,表示個匹配全部。 因此若是不在[]之中,想要匹配'.',就要經過轉意符號\.)

1.2.1. 分類的簡單應用

字符序列:

The lynk is quite a link don't you think? l nk l(nk 複製代碼

正則表達式: l[yi (]nk

結果:

lynk  link  l nk   l(nk
複製代碼

很容易理解的,就是表達邏輯。

1.2.2. 匹配全部可能的電話號碼

好了,如今回到以前遺留的問題,有如下字段,請匹配全部可能的電話號碼:

These are some phone numbers 915-134-3122. Also,
you can call me at 643.123.1333 and of course,
I'm always reachable at (212)867-5509 複製代碼

好的,一步一步來,剛纔咱們使用\d{3}-\d{3}-\d{4}匹配了連字符的狀況。如今咱們能夠很輕鬆的把.這種狀況加進去了

第一步: \d{3}[-.]\d{3}[-.]\d{4}

第二步: 爲了可以匹配括號,可使用?來,由於這是一個option選擇。因此最後就成了

\(?\d{3}[-.)]\d{3}[-.]\d{4}

這裏仍是要說明,在[]中,特殊字符不須要轉義,能夠直接使用,好比[.()],可是在外面,是須要轉義的\( \.

1.2.3. []的特殊語法

剛纔介紹了最簡單和基本的功能,可是有些特殊的地方須要注意

  1. -鏈接符是第一個字符時

好比[-.]的含義是連字符-或者點符.。 可是,若是當連字符不是第一個字符時,好比[a-z],這就表示是從字母a到字符z。

  1. []中的^

^在以前介紹中,是表示一行開頭,可是在[]中,有着不一樣的含義。 [ab] 表示a或者b [^ab] 啥都行,只要不是a或b(anythings except a and b),至關於取反

1.2.4. []和()

除了使用[]表示或邏輯,()也是能夠的。用法是(a|b)表示a或者b

好比下面的例子,匹配全部email

gaoyaqi411@126.com  
dyumc@google.net 
sam@sjtu.edu
複製代碼

思路:

首先要想我到底相匹配什麼,這裏我想匹配的是

  1. 任何一個以words開頭的,一個或更多 \w+
  2. 緊接着是一個@符號 \w+@
  3. 接着有一個或者更多的words \w+@\w+
  4. 接着一個.標點 \w+@\w+\.
  5. 接着一個com netedu \w+@\w+\.(com|net|edu)

仍是提醒注意第四步的\.轉義符號

好了,這樣幾能夠匹配以上的全部郵箱了。可是還有一個問題,由於郵箱用戶名是能夠有.的,好比vincent.ko@126.com

其實仍然很簡單,修復以下: [\w.]+@\w+\.(com|net|edu)

1.2.5. 總結

  1. []的做用,用英文表達就是"alternation",表達一個或的邏輯;
  2. /[-.(]/ 在符號中的連字符-放在第一位表示連字符自己,若是放在中間,表示"從..到..",好比[a-z]表示a-z
  3. [.)] 括號中的特殊符號不須要轉義,就表示其自己
  4. [^ab] 括號中的^表示非,anythings except a and b
  5. (a|b)也可表示選擇,可是它有更強大的功能....

因此,()的強大功能是什麼呢? 分組捕獲,這對序列的替換、交換是頗有幫助的。 後面一節進行學習記錄

1.3. 分組捕獲(capturing groups)

什麼是分組捕獲,如今回到以前電話號碼的例子

212-555-1234
915-412-1333

//我想要保留區號,把後面的電話號碼變爲通用性的
👇👇👇👇👇👇👇👇👇👇👇👇

212-xxx-xxxx
915-xxx-xxxx
複製代碼

按照以前的作法\d{3}-\d{3}-\d{4},這種匹配的方式,是將整個電話號碼做爲一個組(group)匹配起來。 咱們把212-555-1234這樣的叫Group0

這個時候,若是咱們加了一個括號\d{3}-(\d{3})-\d{4},那麼匹配到的555就叫Group1。 以此類推,若是有兩個小括號\d{3}-(\d{3})-(\d{4})那麼分組就是下面的狀況:

212-555-1234   Group0
555            Group1
1234           Group2
複製代碼

1.3.1. 選擇分組

如今組已經分好,那麼如何選擇已經匹配的分組?

這裏有兩種方法,第一種使用$符號,好比$1 表明555,$2表明1234;第二種,使用\,好比\1表明555。兩種的使用場景不同,先講$

如今爲了知足最開始的要求,咱們能夠這麼作

reg: \(?(\d{3})[-.)]\d{3}[-.]\d{4}

replace: $1-xxx-xxxx
複製代碼

ps: 這裏能夠直接用JS的replace函數進行操做,可是正則不是JS專屬的,因此這裏先介紹通用方法,以後對JS部分進行總結

1.3.2. 實景訓練

  1. 如今有一個名單列表,可是姓和名稱是反的,我須要把他交換過來
shiffina, Daniel
shifafl, Daniell
shquer, Danny
...
複製代碼

實現方法:

reg: (\w+),\s(\w+)

replace: $2 $1
複製代碼

注意:$0 是全部匹配到的,因此第一個加括號的是$1

  1. 匹配markdown中的link標籤,並替換爲html標籤
[google](http://google.com)
[itp](http://itp.nyu.edu)
[Coding Rainbow](http://codingrainbow.com)
複製代碼

解析: 這道題有些坑,須要慢慢來。

看到這個,第一個想考慮匹配[google]這個東西,立馬想到正則表達式\[.*\]。 這個是巨大的坑,在當前來看,它的確能正確匹配到上面的三條。 可是若是文本是這樣的:

字符匹配

看到了,第一行的內容會所有匹配下來,而不能區分[google][test]。 之因此這樣,是由於.是貪婪的,他表示全部,全部能匹配到的,因此固然也包括了],一直到這一行的最後一個],它才中止。

因此爲了讓它能正確匹配,須要去掉這種貪婪的屬性。這裏用到 。 當?放在了quantifiers符號後,表示去掉貪婪屬性,匹配到終止條件,便可停下。

\[.*?\]這樣子,就能夠將[google][test]分開,效果以下:

分割

接下來完成全部內容:

reg: \[(.*?)\]\((http.*?)\)

replace: <a href="$2">$1</a>
複製代碼

1.3.3. 使用\選擇器

$選擇符是在替換的時候進行的標誌或選擇,可是若是在正則表達式自己,就要使用\選擇了。好比如下的場景

This is is a a dog , I think think this is is really
a a good good dog. Don't you you thinks so so ? 複製代碼

咱們想要匹配好比is is so so這樣連續的序列,就用到了下面的表達方式: (\w+)\s\1

效果:

效果圖

嗯,差很少達到效果,可是有一些小的bug。好比第一句話This is is a 這個就匹配不許確,會把第一個This的後面字母匹配進去。 這就用到第一節說的字符結界 \b,就變成了\b(\w+)\s\1\b

好了,大功告成,就不貼效果圖了,自行腦補就行了。

1.3.4. 總結

  1. 分組捕獲,使用()進行數據分組,編號0表明整個匹配項,選擇的分組從1號開始
  2. 選擇器可使用$1\1,可是使用場景不一樣,\用在正則表達式本身身上
  3. ?符號能夠禁止貪婪屬性,放在.*以後,表示一次匹配遇到重點就能夠中止。不然將會一直向後匹配。

1.4. 在JavaScript中的應用

在js中,主要的正則表達式都是涉及到string的應用。

var str = "hello"
var r = /w+/
複製代碼

這兩個分別是string和reg的字面量建立方法。當要使用正則來進行操做的時候,使用了r.test()str.match() 以及str.replace等方法。

1.4.1. reg.test()

正則表達式自己有一個test的方法,這個方法只能測試是否包含,返回一個bool變量。

var r = /\d{3}/;
var a = '123';
var b = '123ABC';
var c = 'abc';

r.test(a)  //true
r.test(b) //true
r.test(c) //false
複製代碼

嗯,這個很簡單,並且用的實際很少,下面着重講str上的一些方法。

1.4.2. str.match()

與test()不一樣,不僅是返回bool變量,它會返回你所匹配到的內容。

var r = /compus/
var reg = /w+/
var s = "compus, I know something about you"
r.test(s)  //true
s.match(r)  //["compus"]
s.match(reg) //["compus"]

複製代碼

等等,好像有點問題,爲何最後一個返回的也是"compus"?這不科學。

好吧,實際上,match()返回了第一個能夠匹配的序列。想要實現以前的效果,就要用到JS裏關於正則的幾個flag

1.4.2.1. flag

這個標誌就在創建正則的時候就要有的,主要有三個

flag 含義
g 所有的,給我匹配所有的
i 忽略大小寫
m 多行匹配

因此爲了解決剛纔的問題,只要這樣子設置reg就能夠了

var reg = /w+/g
複製代碼

看下面一個練習

var str = "Here is a Phone Number 111-2313 and 133-2311"

var r = /\d{3}[-.]\d{4}/
var rg = /\d{3}[-.]d{4}/g

console.log(str.match(r)); //["111-2313"]
console.log(str.match(rg));//["111-2313","133-2311"]
複製代碼

嗯,找電話號碼,是的,很方便。可是還有一個問題,剛纔說的分組,那麼match會返回分組嗎?

var sr = /(\d{3})[-.]\d{4}/
var srg = /(\d{3})[-.]\d{4}/g

console.log(str.match(sr)); //["111-2313","111"]
console.log(str.match(srg)); //["111-2313","133-2311"]
複製代碼

因此結論是: 當使用了全局flagg的時候,不會返回分組,而是所有的匹配結果;若是沒有使用g,會將匹配到的結果和分組以數組的形式返回。

那麼如何實現全局的分組?

1.4.3. reg.exec()

從字面意思來看,正則表達式的執行方法。 這個方法能夠實現匹配全局,並返回分組的結果。

reg.exec()每次調用,返回一個匹配的結果,匹配結果和分組以數組的形式返回,不斷的調用便可返回下一個結果,直到返回null

var str = "Here is a Phone Number 111-2313 and 133-2311" ;
var srg = /(\d{3})[-.]\d{4}/g;
var result = srg.exec(str);
while(result !== null) {
    console.log(result);
    result = srg.exec(str);
}

複製代碼

result包含的內容可能比想象中的多,它是一個數組,好比第一次執行,他的結果爲:

["133-2311", "133", index: 36, 
input: "Here is a Phone Number 111-2313 and 133-2311" groups: undefined]
複製代碼

1.4.4. str.split

如今來到了更強的功能上,先說下split,咱們知道split是將字符串按照某個字符分隔開,好比有如下一段話,須要將其分割成單詞。

var s = "unicorns and rainbows And, Cupcakes"
複製代碼

分割成單詞,首先想到的是空格隔開,因而能夠用下面方式實現

var result = s.split(' ');
var result1 = s.split(/\s/);
//徹底同樣的效果
//["unicorns", "and", "rainbows", "And,", "Cupcakes"]
複製代碼

嗯,這樣體現不出來正則的強大,並且最主要的是沒有實現要求。由於還有一個"And,"。因此要用正則了,匹配條件是逗號或者空格

result = s.split(/[,\s]/);

//["unicorns", "and", "rainbows", "And", "", "Cupcakes"]
複製代碼

結果仍然和須要的有出入,由於多了一個""。 咱們並非想讓它分割的依據是逗號或者空格,依據應該是逗號或空格所在的連續序列。 在原來的基礎上加一個+,改爲/[,\s]+/,這個含義就是一個單獨的逗號,或者一個單獨的空格

result = s.split(/[,\s]+/);
// ["unicorns", "and", "rainbows", "And", "Cupcakes"]
複製代碼

1.4.4.1. 單詞分割

好了,拓展一下,實現一個段落的單詞分割,一個正則表達式就是

result = s.split(/[,.!?\s]+/)
複製代碼

固然,有個最簡單的方法,咱們能夠這樣去作

result = s.split(/\W+/);
複製代碼

接着,若是咱們想將一個段落的句子都分隔開,一個能夠實現的表達式就是

result = s.split(/[.,!?]+/)
複製代碼

最後,有一個小需求,就是在分割句子的同時,還想把相應的分隔符保留下來。

var s = 
"Hello,My name is Vincent. Nice to Meet you!What's your name? Haha."
複製代碼

這是一個小小的ponit,記住若是想要保留分隔符,只要給匹配的內容分組便可

var result = s.split(/([.,!?]+)/)
//["Hello", ",", "My name is Vincent", ".", " Nice to Meet you", "!", "What's your name", "?", " Haha", ".", ""]
複製代碼

能夠看到,這樣就會把分隔符也存儲起來。

1.4.5. str.replace()

replace也是字符串的方法,它的基本用法是str.replace(reg,replace|function),第一個參數是正則表達式,表明匹配的內容,第二個參數是替換的字符串或者一個回掉函數。

注意,replace不會修改原字符串,只是返回一個修改後的字符串;除此外,正則表達式若是沒有使用g標誌,也和match同樣,只匹配/替換第一個

1.4.5.1. 最簡單的替換

替換一個序列中的元音字母(aeiou),將其替換成一個double。 好比x->xx

var s = "Hello,My name is Vincent."
var result = s.replace(/([aeiou])/g,"$1$1")
//"Heelloo,My naamee iis Viinceent."
複製代碼

注意,第二個參數必須是字符串; 注意不要忘記加g

1.4.5.2. 牛x哄哄的function參數來了

嗯,這纔是最強大的地方,第二參數傳入function,先看一個最簡單的示例

var s = "Hello,My name is Vincent. What is your name?"
var newStr = s.replace(/\b\w{4}\b/g,replacer)
console.log(newStr)
function replacer(match) {
    console.log(match);
    return match.toUpperCase();
}
/* name What your name Hello,My NAME is Vincent. WHAT is YOUR NAME? */
複製代碼

因此,函數的參數是匹配到的內容,返回的是須要替換的內容。好了,基本示例解釋了基本用法,那麼以前討論的分組怎麼辦?如何實現分組呢?

//分組

function replacer(match,group1,group2) {
    console.log(group1);
    console.log(group2);
}

複製代碼

若是正則表達式分組處理,那麼在回調函數中,函數的第二個、第三參數就是group1,group2。這樣子,就能夠作不少神奇的事情

1.4.5.3. 綜合練習題

  1. 判斷一個字符串中出現次數最多的字符,並統計次數
var s = 'aaabbbcccaaabbbaaa';
var a = s.split('').sort().join("");  //"aaaaaaaaabbbbbbccc"
var ans = a.match(/(\w)\1+/g);
ans.sort(function(a,b) {
    return a.length - b.length;
})
console.log('ans is : ' + ans[ans.length-1])

複製代碼

1.4.6. 總結

  1. 在js中,正則表達式字面量/reg/和字符串字面量"str"用於建立正則和字符串。其中正則上有兩個方法reg.test()reg.exec()
  2. reg.test(str)方法,返回布爾變量,用於指示是否有所匹配; reg.exec(str)有點相似與迭代器,每次執行,返回匹配結果和分組,直到返回爲null結束。
  3. 字符串方法主要有str.match(reg),str.split(reg)str.replace(reg,str|function) 三種方法。
  4. match比較特殊,若是正則包含了分組,且沒有g標誌,則返回匹配內容和分組; 若是沒有分組,且有g標誌,返回全部匹配內容
  5. split方法主要用於字符串分割,若是想要保存分隔符,記得將匹配內容分組(用小括號包起來)
  6. replace是最強大的方法,當使用回掉函數時,返回值就是替換值; 參數分別爲匹配值 group1 group2...
相關文章
相關標籤/搜索