主頁 > 後端開發 > JS 正則運算式詳解 學習筆記

JS 正則運算式詳解 學習筆記

2020-09-16 03:34:51 後端開發

JS 正則運算式:(RegularExpression)

正則運算式的含義:

正則運算式是一種字串匹配模式,在javascript中的作用主要是用來匹配特定形式的字符,

定義一個正則運算式:

我們想要定義一個正則運算式有兩種方法:

  1. 正則運算式直接量
  2. 創建正則運算式物件

正則運算式直接量:

var reg = /s$/

我們可以使用正則運算式直接量來創建一個正則運算式,其中格式為/xxx/,用兩個反斜杠來表示正則運算式創建的格式,反斜杠中間寫上我們要匹配的字符文本,

創建正則運算式物件:

var reg2 = new RegExp("s$")

RegExp建構式創建正則運算式其實也是很簡單的,建構式接收一個引數,引數型別為字串型別,傳入的字串就是我們要匹配的字串的模板,new RegExp()回傳一個正則運算式物件,

注意:和js中其他直接量不同,正則運算式的直接量會在代碼執行的時候會把/s$/轉變成一個RegExp物件,在舊版本的es3中,當執行到同一段正則運算式的時候會回傳相同的物件,舉個例子:

function createRegExp(){
	var reg = /S$/
    return reg
}
var myreg1 = createRegExp()
var myreg2 = createRegExp()
console.log(myreg1 === myreg2)//true

我們定義了一個函式,用來創建一個正則運算式物件,在全域,我們執行了兩次createRegExp()建構式,將生成的正則運算式物件分別存放在myreg1myreg2中,當我們將其做相等運算時發現,它們指向的是相同的一個物件,也就是說,在es3中,用正則運算式常見的RegExp物件會共享同一個實體,其實這個結論的前提是正則運算式的模式要一致,但是在es5中則是兩個相對獨立的運算式物件:

console.log(myreg1 === myreg2)//false

myreg1和myreg2是相互獨立的,并不都會相互共享記憶體資訊,如:lastIndex后續有講到,

直接量字符:

當我們想要去匹配非法的字符的時候,比如/的時候,我們需要對字符進行轉移,通常的做法是在非法的字符前面加上\,即斜杠,

在正則運算式中,有一些字符有它自己獨特的含義,這些字符有:^ $ . * + = ! : | \ / ( ) [ ] { }等,當我們使用這些符號的時候,它代表的意義已經不是我們字面上所看到的了,假如我們真的只是想單純的想去匹配這些字符怎么辦呢,我們需要對這些字符進行轉義,假如我們想要匹配一個+

var reg = /+/

上面的寫法肯定是不行的,原因是它代表特殊的匹配含義,想要單純的匹配字符我們應該這樣:

var reg = /\+/

我們需要用右斜杠將其轉義后就可以單純的匹配+字符了,

字符類:

將直接量字符單獨放進方括號[]內可以組成一個字符類,一個字符類可以匹配它所包含的任意字符,我的理解是,字符類相當于一個集合,這個集合里面放著我們定義的字符,例如:

var reg = /[abc]/

注意:不用打逗號來表示分割,即不要[a,b,c]這樣的話,也成為了我們字符類里的成員,

上面的運算式的意思是:現在我有一個集合,集合里面放的有ab,c這三個元素,那么正則運算式就用這個集合里的任意一個元素進行匹配,另外,我們可以用^來否定字符類,^符號在正則運算式的意思類似于我們編程語言中!的意思,即為非,[^abc]表示剔除掉集合中的所有元素,就是匹配集合的補集,除了集合中的元素,其他的元素都可以匹配,

[]表示的是字符類,但是有一點值得注意,一個[]代表一個字符,只不過這個字符可能是集合中的任意一個字符,什么意思呢,比如:

var str = '1234a'
var reg = /[asdfg]/
//現在我們的集合里面有a,s,d,f,g這五個元素,但是在匹配的時候只能和字串中的一個進行匹配,
//先和str中的1匹配,看自己的集合中有沒有這個字符,如果沒有再和2匹配,看自己的集合里有沒有這個字符
//以此類推,中括號里有五個字符,并不代表它一次能和字串中的五個字符匹配,而是一個一個的匹配,

字符類可以使用連字符-,來表示字符的范圍,如:[a-zA-Z0-9]:表示這個集合里面的元素是大小寫字母和數字,每一個[]其實都有一個自己的集合,就算你這樣寫[(ab)(cd)]他也只是表示他的集合中有(/a/b/)/c/d這六個字符,

在js中,有一些特定的字符類:

  • [...]:方括號內的任意字符,也就是說這個集合里的元素是所有的字符,
  • [^...]:不在方括號內的任意字符,也就是說是[...]集合的補集,也就相當于這個集合為空,它匹配不了任何字符,
  • .:除了換行符和其他Unicode行終止符之外的任意字符,
  • \w:任何ASCII組成的單詞,等價于:[a-zA-Z0-9]
  • \W:(大寫),任何不是ASCII組成的單詞,等價于:[^a-zA-Z0-9]
  • \s:任何Unicode空白符,
  • \S:任何非Unicode空白符的字符,注意:\w\S不一樣,
  • \d:任何ASCII數字,等價于[0-9]
  • \D:除了ASCII數字之外的任何字符,等價于:[^0-9]
  • [\b]:退出直接量(特例)

在方括號內可以寫這些特殊轉義字符,比如:[\w\s]他代表的含義是匹配任何ASCII組成的單詞和任何Unicode空白符,也就是說[\w\s]字符類的集合是\w\s的并集,

重復:

我們現在學的正則運算式只能匹配單個字符,意思是單個單個的字符匹配,無論是字符類還是使用特定的字符類,它們的區別只是對應集合的差異,在匹配的行為上,都是一次只能對一個字符進行匹配,假如我們要對多個字符進行匹配怎么辦,有人會想到,我可以直接/abcd/,這樣就可以一次性匹配四個字符了,也就是四個四個的去匹配,這樣固然可以,但是有局限性,就是限定死了匹配的內容,只能匹配abcd這四個字,現在我們的需求是匹配兩個任意的數字,那么我們應該怎么辦呢?我們可以這樣寫:

var reg = /\d\d/
//這樣就可以表示一次性匹配兩個數字了

但是這樣寫有一個缺點,假如我們要同時匹配100個數字,難道我們要寫100個\d嗎,正則運算式對于重復有自己的相關優化,

下面是正則運算式的重復字符語法:

  • {n,m}:匹配前一項至少n次,但不能超過m次,
  • {n,}:匹配前一項n次或者更多次,
  • {n}:匹配前一項n次,
  • ?:匹配前一項0次或者1次,也就是說前一項是可選的,等價于{0,1}
  • +:匹配前一項1次或多次,等價于{1,}
  • *:匹配前一項0次或多次,等價于{0,}

上面的不是匹配的次數,而是一次要同時匹配幾個字符,即以幾個字符為單位去匹配,

我們來解決我們前面提到的需求,此時我們要匹配兩個數字(兩個兩個的匹配,兩個數字為一次匹配單位),則可以這樣寫:

var reg =/\d{2}/

實體:

這里有幾個實體:

var reg1 = /\d{2,4}/ //匹配2~4個數字
var reg2 = /\w{3}\d?/ //精確匹配三個單詞和一個可選數字
var reg3 = /\s+javascript\s+/ //匹配前后都帶有一個或者多個空格的"javascript"字串
var reg4 = /[^(]*/ //匹配0個或者多個的非(的字符

因為*?允許字符的匹配可以為0,所以它們允許什么都不匹配,

重復的特性:

貪婪匹配:

我們在分析前面正則運算式重復語法的時候發現,大多數的語法都允許我們去匹配多個,現在我們想一個問題,我有一個字串,然后有一個reg

var str = "aaaa"
var reg = /a+/

此時問題就來了,因為+是允許我們去匹配一個或者多個字符,那么我們是匹配一個還是兩個亦或者全部呢?答案是會匹配全部,在一般情況下,正則運算式使用的是貪婪匹配,即盡可能多的匹配,

非貪婪匹配:

假如我們不想進行貪婪匹配,我們可以使用非貪婪匹配,當我們使用非貪婪匹配的時候,只需要在待匹配的字符后面跟隨一個?即可,例如:??,+?,*?,{1,3}?,比如正則運算式/\d{2,4}?/他也可以匹配兩個或四個數字,但是它會盡可能少的匹配,這里挺有意思的,我們仔細思考什么叫盡可能少的匹配,也就是說在默寫特定的情況下即使你使用了?也不能以最少的來匹配,這就引除了一條重要的概念:

正則運算式的模式匹配總是會尋找字串中第一個可能匹配的位置,

例如:

var str = "aaaab"
var reg = /a+?b/

我們也許希望它會匹配最后一個a和第一個b,但是真正的結果是它會匹配整個字串,原因是因為正則運算式的模式匹配總是會尋找字串中第一個可能匹配的位置,

選擇,分組和參考:

正則運算式的語法還包括指定選擇項子運算式分組參考前一子運算式的特殊字符

指定選擇項:在正則運算式中,我們可以使用|來分隔供選擇的字符,例如:/ab|cd|ef/,意思時匹配abcdef,用集合理論的話就是現在的集合中有三個元素ab/cd/ef,看誰最先符合匹配的標準,一旦有一個元素符合匹配的機制就會停止匹配,無論它右面的元素是什么都會被忽略,

選擇項的嘗試匹配次序是從左到右的,直到發現了匹配項,如果左邊的選擇項被匹配的話,則會直接忽略右邊的選擇項,例如:

var str = "ab"
var reg = /a|ab/

因為a被首先的匹配到,所以ab會被自動忽略,

分組()和參考\n

在正則運算式中,圓括號有多種作用:

  1. 把單獨的項組合成子運算式
  2. 在完整的模式中定義子模式
  3. 允許在同一正則運算式的后部分參考前面的子運算式

在正則運算式中,每一個()內的文本都是一個子運算式,有的子運算式可以參考有的卻不能參考,后面有講到,注意:在正則運算式中假如想要匹配’(’ 和 ")"需要進行轉義,

把單獨的項組合成子運算式

? 把單獨的項組合成子運算式,以便可以像處理獨立單元那樣來使用|,?,+等,

? 項其實就是我們每一次進行模式匹配時所用到的字符模板,項的字符數可以是一個或者多個,比如說:

var str = 'ababab'
var reg = /ab|cd|bab/

這里的ab,cd,bab其實就是項,每一次去進行匹配的時候它會使用這三個里面的某一個去匹配,

var str = "abababab"
var str = /ababa/
//此時ababa就是一個項,他是一個整體,不會把它拆開來分別匹配,
var str = "abababab"
var reg = /abab{2}/
console.log(str.match(reg))
null
//我們可以看到,當使用{2}的時候,它前面的項是b,它等價于reg = /ababb/
//所以不同場景,項的字符個數不一樣,

假如我們想要匹配上面字串的abababab怎么辦,當然,你可以直接寫var reg = /abababab/但是這樣寫太麻煩了,我們可以這樣寫/(abab){2}/,此時,{2}會把(abab)看作一個整體,而不是單獨的把最后一個b看作一個整體,其實還有更簡單的方法/(ab){4}/它等價于/abababab/,實體:

var reg = /(ab|cd)+|ef/

上面的正則運算式表示,可以匹配ef,也可以匹配abcd的一次或多次,簡單說,()在這里的作用就是為?/+/*/$/^/{}這些特殊語法服務的,就是想讓它們知道,你要把哪些字符看做一個整體(項),然后一起操作,默認的情況下它們操作的項只有一個字符,

在完整的模式中定義子模式:

允許在同一正則運算式的后部分參考前面的子運算式

帶圓括號運算式的另一個用途是允許在同一正則運算式的后面參考前面的子運算式,這是通過在字符\后面添加數字來實作的,形如:\3原本的數并不需要轉義,但是我們卻用了\字符,說明這個3有特殊含義,數字代表的是帶有圓括號的子運算式在正則運算式的位置,例如:\1代表對正則運算式中的第一個子運算式進行參考,\2表示對正則運算式中的第二個子運算式進行參考,注意,因為子運算式可以嵌套另一個子運算式,所以它的位置是根據參與計數的左括號的位置來決定的,

注意:對正則運算式中前一個子運算式的參考,并不是指對子運算式模式的參考,而指的是與那個模式相匹配的文本的參考,**如何去理解這句話呢,我們看一段代碼:

var reg = /(ab\d)ccc\1/
var str = "ab1cccab1"
console.log(str.match(reg))

我們也許很清楚的知道列印結果:

[ 'ab1cccab1', 'ab1', index: 0, input: 'ab1cccab1', groups: undefined ]

但是匹配的原理真的是你想像中的那樣嗎?我們再來看一個實體:

var reg = /(ab\d)ccc\1/
var str = "ab1cccab2"
console.log(str.match(reg))

結果:

null

為什么是這個樣子,其實上面的那句話說的已經很明了了,對正則運算式前面子運算式的參考并不是對子運算式的模式的參考,什么是模式的參考?模式的參考指的是,比如現在有一個模式是/ab\d/表示匹配ab然后后面跟一個任意的數字,假如有一個正則運算式參考它的模式,則這個正則運算式也表示匹配ab然后后面跟一個任意的數字,但是人家說的很清楚了,不是參考模式而是參考與那個模式相匹配的文本,即\1代表的是(ab\d)匹配的文本,即:ab1,所以當我們設定str = "ab1cccab2"時,就無法得到匹配的原因,

這樣,參考可以用于實施一條約束,即一個字串的各個獨立的部分都是相同的字符,比如:111111111111,假如我們要匹配這樣的一個字串的話,我們完全可以這樣寫:

var str = "aaaaaaaaaaaaa"
var reg = /(\w)(\1)+/
console.log(str.match(reg))

其中/(\w)(\1)+/表示,第一個是任意的字符,那么(\1)表示的是(\w)匹配到的字符,也就是a+表示a的數量可以有多個,那么就可以把整個字串都匹配下來了,

還有一種子運算式:(?:…),例如:(?:java),雖然這是一個運算式,但是它卻不能參考,也就是說它并不在數字索引的考慮范圍之內,例如:

var reg = /(java)(?:script)\2/
var str = "javascriptscript"
console.log(str.match(reg)) 

結果:

null

我們發現,并不能通過\2來獲取script所以(?:)并不能參與正則運算式的數字索引,而它僅僅適用于分組

正則運算式的選擇,分組和參考字符:

  • |:選擇,匹配的是該符號左邊的子運算式或者右邊的子運算式
  • (...):組合,將幾個項組合為一個單元,這個單元可通過*/+/?/|等符號加以修飾,而且可以記住這個組合項匹配的的字串文本以供此后的參考使用,
  • (?:...):只組合,把項組合得到一個單元,這個單元可通過*/+/?/|等符號加以修飾,但不記憶與該組相匹配的字符,其實(?:...)(...)的區別就是一個匹配的文本能被參考而另一個卻不能,
  • \n:n是數字型別,如:\1,和第n個分組第一次匹配的字符相匹配,組是圓括號中的子運算式(也有可能是嵌套的),組的索引是從左到右的左括號數,(?:形式的分組不編碼,

指定匹配位置:

錨點:

在前面我們知道了關于匹配字符的正則運算式,但是,在正則運算式中,還有一種可以用來匹配位置的語法,

在正則運算式中這些匹配位置的語法元素通常我們叫它們錨元素,最常用的錨元素是^,它用來匹配字串的開始,錨元素$用來匹配字串的結束,當然還有\b表示匹配一個單詞的邊界,

零寬斷言:

零寬斷言是一種位置的匹配機制,它匹配到的內容不會保存到匹配結果中去,最終匹配結果只是一個位置而已,
作用是給指定位置添加一個限定條件,用來規定此位置之前或者之后的字符必須滿足限定條件才能使正則中的字運算式匹配成功,

在js中只支持零寬先行斷言,而零寬先行斷言又可以分為正向零寬先行斷言,和負向零寬先行斷言

正向零寬先行斷言(?=…):

var str = "ab123"
var reg = /ab(?=\d+)/
console.log(str.match(reg))
//[ 'ab', index: 0, input: 'ab123', groups: undefined ]

在這里,(?=\d+)表示的是正向零寬斷言,意思是匹配的字符后面(是一個位置概念,類似于^和$)必須有一個或者多個數字才能匹配成功,我們發現,最后它回傳的是ab,并沒有把后面的數字給回傳,因為零寬斷言(?=\d+)并不匹配任何字符,只是用來規定當前位置的后面必須是一個或多個數字,也就是位置的限制條件,要把ab匹配成功,那么它的后面一定要有一個或多個的數字,我們發現,其實和我們熟知的錨點不同,錨點的位置匹配是固定的,沒有任何的限制條件,但是零寬斷言就不一樣了,它必須有限制,

負向零寬先行斷言(?!..):

var str = "ab123abc"
var reg = /ab(?!\d+)/
console.log(str.match(reg))
//[ 'ab', index: 5, input: 'ab123abc', groups: undefined ]

在這里,(?!...)表示的是負向先行斷言,意思時匹配的字符后面必須沒有一個或多個數字才能匹配成功,和正向零寬先行斷言一樣,負向的也只回傳ab,因為零寬斷言(?!\d+)并不匹配任何字符,只是用來規定當前位置的后面必須不能是一個或多個數字,也就是位置的限制條件,這里回傳的index值為5,說明它匹配的ab并不是開頭的那個,而是后面的,我們發現其實正向零寬斷言是表示?=\d+,即這個位置必須有(等于)這個限制條件,而負向零寬斷言是表示?!\d+,即這個位置必須不能有(不等于)這個條件,其實也就是字面意思!(非)不等于或者和正向的條件相反,

通過這兩個實體,其實我們對零寬斷言有一些的了解,我認為零寬斷言彌補了正則中在字串中間定位位置的機制,我們所熟知的錨點是在邊界上定位位置,

修飾符:

正則運算式的修飾符用于更高級的模式匹配,修飾符的寫法和我們平時的匹配模板不同,他并不是用在雙斜杠內部的,而是外部,在第二個反斜杠的右側,如:/\d/gg就是我們所說的修飾符,

正則運算式修飾符:

  • i:執行不區分大小寫,
  • g:執行一個全域匹配,簡而言之,即 找到所有的匹配,而不是在找到第一個后就停止
  • m:多行匹配,^匹配一行的開頭和字串的開頭,$匹配行的結束和字串的結束,

修飾符可以多個同時存在,比如:/javascript\d+/ig表示匹配是全域匹配且不區分大小寫,

用于模式匹配的String方法:

對于正則匹配,我們一般有兩個側重點,一個是對字串的側重,另一個是對正則運算式的側重,

其中在字串中,我們有四種方法使用正則運算式的方法:

  1. search(reg):search方法有一個引數,代表接收的正則運算式,該方法回傳的是第一個符合匹配的起始位置,如果匹配失敗則回傳-1search不支持全域檢索也即是說它會忽略修飾符g,實體:

    console.log("1234".search(/\d/g))
    //0
    

    假如傳入的引數不是正則運算式,則search會將其轉換成正則運算式,例如:str.search("123")會轉換成str.search(/123/)

  2. replace(reg,str):該方法的作用是執行檢索和替換,它接收兩個引數,第一個是要進行匹配的正則運算式,第二個引數是參與替換的字串,該方法會對要匹配的字串進行檢索,將匹配成功的字串替換成第二個引數中的字串,回傳一個替換好的新串,假如使用了模式匹配修飾符g,則不會只匹配一個,而是將整個字串都檢索,符合匹配的都被替換,如果傳入的第一個引數是一個字串,則會直接搜索這個字串,然后進行替換,且只替換一次,因為它并沒有收到全域匹配的指令**,如果在替換字串中出現了$符號加數字,那么replace將用與指定的子運算式相匹配的文本來替換這兩個字符,**replace方法的第二個引數可以是函式,

    該函式能夠動態的機選替換字串,

    插入特殊變數名:

    $$插入一個 “$”,
    $&插入匹配的子串,
    $插入當前匹配的子串左邊的內容,
    $插入當前匹配的子串右邊的內容,
    $n假如第一個引數是 RegExp物件,并且 n 是個小于100的非負整數,那么插入第 n 個括號匹配的字串,提示:索引是從1開始

    實體:

    var str = "asdfg12dfsdf34"
    var reg = /(\d)(\d)/g
    console.log(str.replace(reg,"$2"))
    //asdfg2dfsdf4
    //運行程序大致為,先匹配到了"12",然后用第二個子運算式的文本,也就是"2"來替換匹配到的"12"字串,
    //然后繼續向下匹配,匹配到"34",然后用第二個子運算式的文本,也就是"4"來替換匹配到的"34"字串,
    //然后回傳新串
    

    這里我們可以看到我們用$2匹配到的文本替換了reg匹配到的值,其實本質上,我們就是用第二個引數的值來替換匹配到的值,那么我們就可以靈活發揮了,例如:

    var str = "asdfg12dfsdf34"
    var reg = /(\d)(\d)/g
    console.log(str.replace(reg,"$2" + "AAAA"))
    //asdfg2AAAAdfsdf4AAAA
    //運行程序是這樣的,我們先匹配到了"12",然后$2代表的是第二個子運算式匹配的文本,即"2",然后進行一個
    //字串加法運算,然后把運算的結果當作參與替換的字串,其實本質上,$2就是一個動態的實時再在的值,
    //之所以是動態的,原因在于每次匹配,第二個子運算式的 值會有所變化,
    

    replac方法中的第二個引數可以為一個函式,我們可以通過函式來對匹配的字符進行操作:

    var str = "asdfg12dfsdf34"
    var reg = /\d\d/g
    console.log(str.replace(reg,function(val){
        console.log(val)
        return "AA" + val
    }))
    

    結果:

    12
    34
    asdfgAA12dfsdfAA34
    
  3. match(reg):該方法是最常用的方法之一,該方法只有一個引數,該引數是我們要匹配的正則運算式,該方法的回傳值是一個陣列,若我們使用了修飾符g,則該方法會進行全域匹配,而回傳的陣列中就是全域匹配成功的字符,假如我們并不進行全域匹配,那么仍然回傳一個陣列,該陣列的一個元素是我們匹配到的字符,而剩下的元素是子運算式匹配的文本,如:

    var str = "123"
    var reg2 = /\d/
    var reg1 = /\d/g
    var reg = /(\d)(\d)(\d)/
    console.log(str.match(reg))
    console.log(str.match(reg1))
    console.log(str.match(reg2))
    

    結果:

    [ '123', '1', '2', '3', index: 0, input: '123', groups: undefined ]
    [ '1', '2', '3' ]
    [ '1', index: 0, input: '123', groups: undefined ]
    
  4. split(string/reg):該方法用以將呼叫它的字串拆分為一個子串組成的陣列,使用的分割符就是傳入的引數,例如

    console.log("1,2,3,4,5".split(","))
    //[ '1', '2', '3', '4', '5' ]
    console.log("1-2-3-4-5".split("-"))
    //['1','2','3','4','5']
    console.log("1 - 2".split(/\s-\s/))
    //['1','2']
    console.log("1 - 2 -        3".split(/\s*-\s*/))
    //[ '1', '2', '3' ]
    

    當我們傳入正則運算式時,split會在字串中匹配相應的字符,將匹配成功的字符作為分隔符來進行分割字串,

RegExp物件:

我們在前面說過,當js引擎在對正則運算式的處理是:假如我們用直接量的方式去創建一個正則運算式,那么js引擎會把它轉化成正則物件,想要獲取正則運算式物件,可以通過RegExp建構式:

var reg = new RegExp("abc")

這樣我們就獲得了一個匹配字串abc的一個正則運算式物件,RegExp可以接收兩個字串引數,第一個引數是我們要匹配的字串模板,也就是直接量正則運算式兩個斜杠之間的文本,在這里需要注意,假如我們使用像\d\s\w\W等這種帶有\字符的這種字符類,必須要把\進行轉義,比如:

var reg = new RegExp("\\d")

在這里我們使用了\\d中的\進行了轉義,RegExp建構式的第二個引數是一個可選引數,它指定的是正則運算式的修飾符,不過只能傳入i/g/m或者它們的組合,例如:

var reg = new RegExp("\\d","g")

既然是物件,那么就會有屬性和方法,接下來我們來看一看正則運算式物件的屬性和方法,

屬性:

  1. source:只讀,字串型別,包含正則運算式的文本,
  2. global:只讀,布爾型別,用以說明運算式是否含有修飾符g
  3. ignoreCase:只讀,布爾型別,用以說明運算式是否含有修飾符i
  4. multiline:只讀,布爾型別,用以說明運算式是否含有修飾符m
  5. lastIndex:可讀寫,number型別,如果匹配模式中使用了修飾符g,這個屬性存盤的是在整個字串下一次檢索開始的位置,這個屬性在后面的物件方法中會用到,

方法:

  1. exec():該函式的第一個引數是待檢索的字串,可以傳入第二引數,第二個引數是修飾符,實體:

    var str = "abcde"
    var reg = new RegExp("\\w\\w")
    console.log(reg.exec(str))
    //[ 'ab', index: 0, input: 'abcde', groups: undefined ]
    

    我們從代碼中可以看到,exec方法對一個指定的字串執行一個正則運算式,簡言之,就是在一個字符中執行檢索匹配,如果沒有找到任何匹配那就回傳null,但是如果找到了一個匹配,它將回傳一個陣列,然后不會再向后進行匹配,這個陣列的第一個元素包含的是與正則運算式相匹配的字串,余下的元素是與圓括號內的子運算式相匹配的子串,例如:

    var str = "abcde"
    var reg = new RegExp("(\\w\\w)(\\w\\w)")
    console.log(reg.exec(str))
    //[ 'abcd', 'ab', 'cd', index: 0, input: 'abcde', groups: undefined ]
    

    match方法不同,就算是你使用了修飾符g,單次執行該函式是不會去進行全域的匹配,實體:

    var str = "123"
    var reg = new RegExp("\\d","g")
    console.log(reg.exec(str))
    //[ '1', index: 0, input: '123', groups: undefined ]
    

    match方法單次執行會進行全域匹配:

    var str = "123"
    var reg = new RegExp("\\d","g")
    console.log(str.match(reg))
    //[ '1', '2', '3' ]
    

    雖然exec的功能簡單,但是多次執行時有一點特別需要注意,那就是它是否帶有修飾符g,我們來看一個例子:

    var str = "abcdefghijklmnopqrst"
    var reg = new RegExp("\\w\\w")
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    /*
    [ 'ab', index: 0, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'ab', index: 0, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'ab', index: 0, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'ab', index: 0, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'ab', index: 0, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'ab', index: 0, input: 'abcdefghijklmnopqrst', groups: undefined ]
    */
    

    然后我們再來看一個例子:

    var str = "abcdefghijklmnopqrst"
    var reg = new RegExp("\\w\\w","g")
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    /*
    [ 'ab', index: 0, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'cd', index: 2, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'ef', index: 4, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'gh', index: 6, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'ij', index: 8, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'kl', index: 10, input: 'abcdefghijklmnopqrst', groups: undefined ]
    */
    

    我們觀察上面的代碼,我們可以發現,第一個實體是我們不加修飾符g,第二個實體我們加上了修飾符g,我們連續對同一個字串呼叫exec我們可以從列印的結果上看,它們有天壤之別,在第一個實體當中,匹配的元素永遠是ab,且index屬性的值永遠是0,而第二個實體中,匹配的是不同的字符,并且index屬性的值一直是變化的,這就是修飾符gexec函式的宏觀影響,當呼叫exec函式的正則運算式物件具有修飾符g時,它將把當前正則運算式物件的lastIndex屬性設定為緊挨著匹配子串的字符位置,當同一正則運算式第二次呼叫exec函式時,他將從lastIndex所指示的字符處開始檢索,如果exec沒有檢測到任何匹配結果,他會將lastIndex重置為0,這里有一個很有意思的實體:

    var str = "abcdefghijklmnopqrst"
    var str_2 = "zzxxccvvbb"
    var reg = new RegExp("\\w\\w","g")
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    console.log(reg.exec(str_2))
    /*
    [ 'ab', index: 0, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'cd', index: 2, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'cc', index: 4, input: 'zzxxccvvbb', groups: undefined ]
    */
    

    我們可以從實體中對上面的描述會有一個更清楚的理解,當我們第一次執行reg.exec(str)后,此時的reg.lastIndex的值為2,當我們第二次執行reg.exec(str)時,因為需要從reg物件中的lastindex屬性中獲取我檢索的位子,此時的reg.lastIndex的值為2,所以就從2位置開始檢索,檢索完畢后將reg.lastIndex的值設定為4并保存,當去匹配其他字串的時候,需要從lastIndex獲取開始匹配的位置,所以其他的字串會從4這個位置開始匹配,

  2. test(str):該函式接收一個引數,就是我們所要匹配的字串,該函式的作用是檢測目標字串中是否有我們要匹配的字符,如果有就回傳true,如果沒有就回傳false,實體:

    var str = "1asdf"
    var reg = new RegExp("\\d")
    console.log(reg.test(str))
    //true
    

    當我們不添加修飾符g的時候,和exec方法一樣,reg.lastIndex的值永遠為0,每一次的匹配都要從最開始來匹配,但是當我們加上修飾符g的時候,事情就變得好玩了,我們來看一個實體:

    var str = "asd1f"
    var reg = new RegExp("\\d","g")
    console.log(reg.lastIndex)
    console.log(reg.test(str))
    console.log(reg.lastIndex)
    console.log(reg.test(str))
    console.log(reg.lastIndex)
    /*
    0
    true
    4
    false
    0
    */
    

    我們發現,當我們使用修飾符g的時候,和exec一樣,當匹配成功時,lastIndex會被設定為緊挨著匹配子串的字符位置,當有其他的字符呼叫該正則運算式的test函式時,開始檢索的位置就是reg.lastIndex保存的位置,當匹配失敗,則會重置lastIndex的值為0

    exectest不同的是,字串上的方法并不會用到lastIndex這個屬性,本質上,String方法只是簡單地將lastIndex的值重置為0,所以我們每次呼叫相關方法的時候都會從頭開始檢索,

感想:

上述的文章是我在學習js正則運算式自己歸納和理解的,僅供大家參考,若有錯誤,望指正,我將感激不盡,謝謝!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/52621.html

標籤:python

上一篇:css中display屬性作用大全

下一篇:制作游戲時,如何管理游戲資源? 逐行注釋!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more