一次性搞懂JavaScript正規表示式之語法

馬蹄疾發表於2018-11-01

本文是『horseshoe·Regex專題』系列文章之一,後續會有更多專題推出

GitHub地址:github.com/veedrin/hor…

部落格地址(文章排版真的很漂亮):matiji.cn

如果覺得對你有幫助,歡迎來GitHub點Star或者來我的部落格親口告訴我


名餘曰正則兮,字餘曰靈均。

Regular Expressions翻譯成中文叫正規表示式。也不知道是誰翻譯過來的,聽起來就很嚴肅。似乎翻譯成通用表示式更能傳達其精髓,如果你不怕夢見屈原的話。

為什麼叫通用表示式?因為它有一套和程式語言無關的文字匹配規則。很多語言都實現了正規表示式的文字匹配引擎,只不過在功能集合上略有不同。

我們要記住的是三點:

其一,正規表示式是用來提取文字的。

其二,正規表示式的表達能力強大到令人髮指。

其三,正規表示式的語法對初學者不友好。

另外,本專題只涉及JavaScript語言的正規表示式,其他語言的規則可能略有不同。

我還為各位讀者準備了一副宣傳語,應該能讓你心動(點贊)吧?

學一門前端工具,幾年就過時了。學正規表示式,受用一輩子。

普通字元

什麼叫普通字元?

當我們寫a的時候,我們指的就是a;當我們寫的時候,我們指的就是

'hello ? regex'.match(/?/);
// ["?", index: 6, input: "hello ? regex", groups: undefined]
複製程式碼

這就是普通字元,它在正則中的含義就是檢索它本身。除了正則規定的部分字元外,其餘的都是普通字元,包括各種人類語言,包括emoji,只要能夠表達為字串。

開始與結束

^字元的英文是caret,翻譯成中文是脫字元。不要問我,又不是我翻譯的。它在正則中屬於元字元,通常代表的意義是文字的開始。說通常是因為當它在字元組中[^abc]另有含義。

什麼叫文字的開始?就是如果它是正則主體的第一個符號,那緊跟著它的字元必須是被匹配文字的第一個字元。

'regex'.match(/^r/);
// ["r", index: 0, input: "regex", groups: undefined]
複製程式碼

問題來了,如果^不是正則的第一個符號呢?

'regex'.match(/a^r/);
// null
複製程式碼

所以呀,關於它有三點需要注意:

  • 作為匹配文字開始元字元的時候必須是正則主體的第一個符號,否則正則無效。
  • 它匹配的是一個位置,而不是具體的文字。
  • 它在其他規則中有另外的含義。

$字元與^正好相反。它代表文字的結束,並且沒有其他含義(其實是有的,但不是在正則主體內)。同樣,它必須是正則主體的最後一個符號。

'regex'.match(/x$/);
// ["x", index: 4, input: "regex", groups: undefined]
複製程式碼

^$特殊的地方在於它匹配的是一個位置。位置不像字元,它看不見,所以更不容易理解。

轉義

我們現在已經知道$匹配文字的結束位置,它是元字元。但是如果我想匹配$本身呢?匹配一個美元符號的需求再常見不過了吧。所以我們得將它貶為庶民。

\反斜槓就是幹這個的。

'price: $3.6'.match(/\$[0-9]+\.[0-9]+$/);
// ["$3.6", index: 7, input: "price: $3.6", groups: undefined]
複製程式碼

上面的例子有點超綱了,超綱的部分先不管。

你可以認為\也是一個元字元,它跟在另一個元字元後面,就能還原它本來的含義。

如果有兩個\呢?那就是轉義自身了。如果有三個\呢?我們得分成兩段去理解。以此類推。

普通字元前面跟了一個\是什麼效果?首先它們是一個整體,然後普通字元轉義後還是普通字元。

帶反斜槓的元字元

一般來說,普通字元前面帶反斜槓還是普通字元,但是有一些普通字元,帶反斜槓後反而變成了元字元。

要怪只能怪計算機領域的常用符號太少了。

元字元 含義
\b 匹配一個單詞邊界(boundary)
\B 匹配一個非單詞邊界
\d 匹配一個數字字元(digit)
\D 匹配一個非數字字元
\s 匹配一個空白字元(space)
\S 匹配一個非空白字元
\w 匹配一個字母或者一個數字或者一個下劃線(word)
\W 匹配一個字母、數字和下劃線之外的字元

你這麼聰明,肯定一眼就看出來,大寫代表反義。對,就是這麼好記。

\b元字元

\b匹配的也是一個位置,而不是一個字元。單詞和空格之間的位置,就是所謂單詞邊界。

'hello regex'.match(/\bregex$/);
// ["regex", index: 6, input: "hello regex", groups: undefined]
'hello regex'.match(/\Bregex$/);
// null
複製程式碼

所謂單詞邊界,對中文等其他語言是無效的。

'jiangshuying gaoyuanyuan huosiyan'.match(/\bgaoyuanyuan\b/);
// ["gaoyuanyuan", index: 13, input: "jiangshuying gaoyuanyuan huosiyan", groups: undefined]
'江疏影 高圓圓 霍思燕'.match(/\b高圓圓\b/);
// null
複製程式碼

所以\b翻譯一下就是^\w|\w$|\W\w|\w\W

\d元字元

\d匹配一個數字,注意,這裡的數字不是指JavaScript中的數字型別,因為文字全是字串。它指的是代表數字的字元。

'123'.match(/\d/);
// ["1", index: 0, input: "123", groups: undefined]
複製程式碼

\s元字元

\s匹配一個空白字元。

這裡需要解釋一下什麼是空白字元。

空白字元不是空格,它是空格的超集。很多人說它是\f\n\r\t\v的總和,其中\f是換頁符,\n是換行符,\r是回車符,\t是水平製表符,\v是垂直製表符。是這樣麼?

'a b'.match(/\w\s\w/);
// ["a b", index: 0, input: "a b", groups: undefined]
'a b'.match(/\w\f\w/);
// null
'a b'.match(/\w\n\w/);
// null
'a b'.match(/\w\r\w/);
// null
'a b'.match(/\w\t\w/);
// null
'a b'.match(/\w\v\w/);
// null
'a b'.match(/\w \w/);
// ["a b", index: 0, input: "a b", groups: undefined]
複製程式碼

這樣說的人,明顯是沒有做過實驗。其實正確的寫法是空格\f\n\r\t\v的總和,集合裡面包含一個空格,可千萬別忽略了。誒,難道空格在正則中的寫法就是空一格麼,是的,就是這樣隨意。

這個集合中很多都是不可列印字元,估計只有\n是我們的老朋友。所以,如果不需要區分空格和換行的話,那就大膽的用\s吧。

\w元字元

\w匹配一個字母或者一個數字或者一個下劃線。為什麼要將它們放一起?想一想JavaScript中的變數規則,包括很多應用的使用者名稱都只能是這三樣,所以把它們放一起挺方便的。

不過要注意,字母指的是26個英文字母,其他的不行。

'正則'.match(/\w/);
// null
複製程式碼

負陰抱陽

如果我們將大寫和小寫的帶反斜槓的元字元組合在一起,就能匹配任何字元。是的,不針對任何人。

'@regex'.match(/[\s\S]/);
// ["@", index: 0, input: "@regex", groups: undefined]
複製程式碼

方括號的含義我們先按下不表。

道生一

.在正則中的含義仙風道骨,它匹配換行符之外的任意單個字元。

如果文字不存在換行符,那麼.[\b\B][\d\D][\s\S][\w\W]是等價的。

如果文字存在換行符,那麼(.|\n)[\b\B][\d\D][\s\S][\w\W]是等價的。

'@regex'.match(/./);
// ["@", index: 0, input: "@regex", groups: undefined]
複製程式碼

量詞

前面我們一直在強調,一個元字元只匹配一個字元。即便強大如.它也只能匹配一個。

那匹配gooooogle的正則是不是得寫成/gooooogle/呢?

正則冷笑,並向你發射一個蔑視。

如果匹配的模式有重複,我們可以宣告它重複的次數。

量詞 含義
? 重複零次或者一次
+ 重複一次或者多次,也就是至少一次
* 重複零次或者多次,也就是任意次數
{n} 重複n次
{n,} 重複n次或者更多次
{n,m} 重複n次到m次之間的次數,包含n次和m次

有三點需要注意:

  • ?在諸如匹配http協議的時候非常有用,就像這樣:/http(s)?/。它在正則中除了是量詞還有別的含義,後面會提到。

  • 我們習慣用/.*/來匹配若干對我們沒有價值的文字,它的含義是若干除換行符之外的字元。比如我們需要文字兩頭的格式化資訊,中間是什麼無所謂,它就派上用場了。不過它的效能可不好。

  • {n,m}之間不能有空格,空格在正則中是有含義的。

關於量詞最令人困惑的是:它重複什麼?

它重複緊貼在它前面的某個集合。第一點,必須是緊貼在它前面;第二點,重複一個集合。最常見的集合就是一個字元,當然正則中有一些元字元能夠將若干字元變成一個集合,後面會講到。

'gooooogle'.match(/go{2,5}gle/);
// ["gooooogle", index: 0, input: "gooooogle", groups: undefined]
複製程式碼

如果一個量詞緊貼在另一個量詞後面會怎樣?

'gooooogle'.match(/go{2,5}+gle/);
// Uncaught SyntaxError: Invalid regular expression: /go{2,5}+gle/: Nothing to repeat
複製程式碼

貪婪模式與非貪婪模式

前面提到量詞不能緊跟在另一個量詞後面,馬上要??打臉了。

'https'.match(/http(s)?/);
// ["https", "s", index: 0, input: "https", groups: undefined]
'https'.match(/http(s)??/);
// ["http", undefined, index: 0, input: "https", groups: undefined]
複製程式碼

然而,我的臉是這麼好打的?

緊跟在?後面的?它不是一個量詞,而是一個模式切換符,從貪婪模式切換到非貪婪模式。

貪婪模式在正則中是預設的模式,就是在既定規則之下匹配儘可能多的文字。因為正則中有量詞,它的重複次數可能是一個區間,這就有了取捨。

緊跟在量詞之後加上?就可以開啟非貪婪模式。怎麼省事怎麼來。

這裡的要點是,?必須緊跟著量詞,否則的話它自己就變成量詞了。

字元組

正則中的普通字元只能匹配它自己。如果我要匹配一個普通字元,但是我不確定它是什麼,怎麼辦?

'grey or gray'.match(/gr[ae]y/);
// ["grey", index: 0, input: "grey or gray", groups: undefined]
複製程式碼

方括號在正則中表示一個區間,我們稱它為字元組。

首先,字元組中的字符集合只是所有的可選項,最終它只能匹配一個字元。

然後,字元組是一個獨立的世界,元字元不需要轉義。

'$'.match(/[$&@]/);
// ["$", index: 0, input: "$", groups: undefined]
複製程式碼

最後,有兩個字元在字元組中有特殊含義。

^在字元組中表示取反,不再是文字開始的位置了。

'regex'.match(/[^abc]/);
// ["r", index: 0, input: "regex", groups: undefined]
複製程式碼

如果我就要^呢?前面已經講過了,轉義。

-本來是一個普通字元,在字元組中搖身一變成為連字元。

'13'.match(/[1-9]3/);
// ["13", index: 0, input: "13", groups: undefined]
複製程式碼

連字元的意思是匹配範圍在它的左邊字元和右邊字元之間。

如果我這樣呢?

'abc-3'.match(/[0-z]/);
// ["a", index: 0, input: "abc-3", groups: undefined]
複製程式碼
'xyz-3'.match(/[0-c]/);
// ["3", index: 4, input: "xyz-3", groups: undefined]
複製程式碼
'xyz-3'.match(/[0-$]/);
// Uncaught SyntaxError: Invalid regular expression: /[0-$]/: Range out of order in character class
複製程式碼

發現什麼了沒有?只有兩種字元是可以用連字元的:英文字母和數字。而且英文字母可以和數字連起來,英文字母的順序在後面。這和撲克牌1 2 3 4 5 6 7 8 9 10 J Q K是一個道理。

捕獲組與非捕獲組

我們已經知道量詞是怎麼回事了,我們也知道量詞只能重複緊貼在它前面的字元。

如果我要重複的是一串字元呢?

'i love you very very very much'.match(/i love you very +much/);
// null
'i love you very very very much'.match(/i love you v+e+r+y+ +much/);
// null
複製程式碼

這樣肯定是不行的。是時候請圓括號出山了。

'i love you very very very much'.match(/i love you (very )+much/);
// ["i love you very very very much", "very ", index: 0, input: "i love you very very very much", groups: undefined]
複製程式碼

圓括號的意思是將它其中的字符集合打包成一個整體,然後量詞就可以操作這個整體了。這和方括號的效果是完全不一樣的。

而且預設的,圓括號的匹配結果是可以捕獲的。

正則內捕獲

現在我們有一個需求,匹配<div>標籤。

'<div>hello regex</div>'.match(/<div>.*<\/div>/);
// ["<div>hello regex</div>", index: 0, input: "<div>hello regex</div>", groups: undefined]
複製程式碼

這很簡單。但如果我要匹配的是任意標籤,包括自定義的標籤呢?

'<App>hello regex</App>'.match(/<([a-zA-Z]+)>.*<\/\1>/);
// ["<App>hello regex</App>", "App", index: 0, input: "<App>hello regex</App>", groups: undefined]
複製程式碼

這時候就要用到正則的捕獲特性。正則內捕獲使用\數字的形式,分別對應前面的圓括號捕獲的內容。這種捕獲的引用也叫反向引用

我們來看一個更復雜的情況:

'<App>hello regex</App><p>A</p><p>hello regex</p>'.match(/<((A|a)pp)>(hello regex)+<\/\1><p>\2<\/p><p>\3<\/p>/);
// ["<App>hello regex</App><p>A</p><p>hello regex</p>", "App", "A", "hello regex", index: 0, input: "<App>hello regex</App><p>A</p><p>hello regex</p>", groups: undefined]
複製程式碼

如果有巢狀的圓括號,那麼捕獲的引用是先遞迴的,然後才是下一個頂級捕獲。

正則外捕獲

'@abc'.match(/@(abc)/);
// ["@abc", "abc", index: 0, input: "@abc", groups: undefined]
RegExp.$1;
// "abc"
複製程式碼

沒錯,RegExp就是構造正則的建構函式。如果有捕獲組,它的例項屬性$數字會顯示對應的引用。

如果有多個正則呢?

'@abc'.match(/@(abc)/);
// ["@abc", "abc", index: 0, input: "@abc", groups: undefined]
'@xyz'.match(/@(xyz)/);
// ["@xyz", "xyz", index: 0, input: "@xyz", groups: undefined]
RegExp.$1;
// "xyz"
複製程式碼

RegExp建構函式的引用只顯示最後一個正則的捕獲。

另外還有一個字串例項方法也支援正則捕獲的引用,它就是replace方法。

'hello **regex**'.replace(/\*{2}(.*)\*{2}/, '<strong>$1</strong>');
// "hello <strong>regex</strong>"
複製程式碼

實際上它才是最常用的引用捕獲的方式。

捕獲命名

這是ES2018的新特性。

使用\數字引用捕獲必須保證捕獲組的順序不變。現在開發者可以給捕獲組命名了,有了名字以後,引用起來更加確定。

'<App>hello regex</App>'.match(/<(?<tag>[a-zA-Z]+)>.*<\/\k<tag>>/);
// ["<App>hello regex</App>", "App", index: 0, input: "<App>hello regex</App>", groups: {tag: "App"}]
複製程式碼

在捕獲組內部最前面加上?<key>,它就被命名了。使用\k<key>語法就可以引用已經命名的捕獲組。

是不是很簡單?

通常情況下,開發者只是想在正則中將某些字元當成一個整體看待。捕獲組很棒,但是它做了額外的事情,肯定需要額外的記憶體佔用和計算資源。於是正則又有了非捕獲組的概念。

'@abc'.match(/@(abc)/);
// ["@abc", "abc", index: 0, input: "@abc", groups: undefined]
'@abc'.match(/@(?:abc)/);
// ["@abc", index: 0, input: "@abc", groups: undefined]
複製程式碼

只要在圓括號內最前面加上?:標識,就是告訴正則引擎:我只要這個整體,不需要它的引用,你就別費勁了。從上面的例子也可以看出來,match方法返回的結果有些許不一樣。

個人觀點:我覺得正則的捕獲設計應該反過來,預設不捕獲,加上?:標識後才捕獲。因為大多數時候開發者是不需要捕獲的,但是它又懶得加?:標識,會有些許效能浪費。

分支

有時候開發者需要在正則中使用或者

'高圓圓'.match(/陳喬恩|高圓圓/);
// ["高圓圓", index: 0, input: "高圓圓", groups: undefined]
複製程式碼

|就代表或者。字元組其實也是一個多選結構,但是它們倆有本質區別。字元組最終只能匹配一個字元,而分支匹配的是左邊所有的字元或者右邊所有的字元。

我們來看一個例子:

'我喜歡高圓圓'.match(/我喜歡陳喬恩|高圓圓/);
// ["高圓圓", index: 3, input: "我喜歡高圓圓", groups: undefined]
複製程式碼

因為|是將左右兩邊一切兩半,然後匹配左邊或者右邊。所以上面的正則顯然達不到我們想要的效果。這個時候就需要一個東西來縮小分支的範圍。誒,你可能已經想到了:

'我喜歡高圓圓'.match(/我喜歡(?:陳喬恩|高圓圓)/);
// ["我喜歡高圓圓", index: 0, input: "我喜歡高圓圓", groups: undefined]
複製程式碼

沒錯,就是圓括號。

零寬斷言

正則中有一些元字元,它不匹配字元,而是匹配一個位置。比如之前提到的^$^的意思是說這個位置應該是文字開始的位置。

正則還有一些比較高階的匹配位置的語法,它匹配的是:在這個位置之前或之後應該有什麼內容。

零寬(zero-width)是什麼意思?指的就是它匹配一個位置,本身沒有寬度。

斷言(assertion)是什麼意思?指的是一種判斷,斷言之前或之後應該有什麼或應該沒有什麼。

零寬肯定先行斷言

所謂的肯定就是判斷有什麼,而不是判斷沒有什麼。

而先行指的是向前看(lookahead),斷言的這個位置是為前面的規則服務的。

語法很簡單:圓括號內最左邊加上?=標識。

'CoffeeScript JavaScript javascript'.match(/\b\w{4}(?=Script\b)/);
// ["Java", index: 13, input: "CoffeeScript JavaScript javascript", groups: undefined]
複製程式碼

上面匹配的是四個字母,這四個字母要滿足以下條件:緊跟著的應該是Script字串,而且Script字串應該是單詞的結尾部分。

所以,零寬肯定先行斷言的意思是:現在有一段正則語法,用這段語法去匹配給定的文字。但是,滿足條件的文字不僅要匹配這段語法,緊跟著它的必須是一個位置,這個位置又必須滿足一段正則語法。

說的再直白點,我要匹配一段文字,但是這段文字後面必須緊跟著另一段特定的文字。零寬肯定先行斷言就是一個界碑,我要滿足前面和後面所有的條件,但是我只要前面的文字。

我們來看另一種情況:

'CoffeeScript JavaScript javascript'.match(/\b\w{4}(?=Script\b)\w+/);
// ["JavaScript", index: 13, input: "CoffeeScript JavaScript javascript", groups: undefined]
複製程式碼

上面的例子更加直觀,零寬肯定先行斷言已經匹配過Script一次了,後面的\w+卻還是能匹配Script成功,足以說明它的零寬特性。它為緊貼在它前面的規則服務,並且不影響後面的匹配規則。

零寬肯定後行斷言

先行是向前看,那後行就是向後看(lookbehind)咯。

語法是圓括號內最左邊加上?<=標識。

'演員高圓圓 將軍霍去病 演員霍思燕'.match(/(?<=演員)霍\S+/);
// ["霍思燕", index: 14, input: "演員高圓圓 將軍霍去病 演員霍思燕", groups: undefined]
複製程式碼

一個正則可以有多個斷言:

'演員高圓圓 將軍霍去病 演員霍思燕'.match(/(?<=演員)霍.+?(?=\s|$)/);
// ["霍思燕", index: 14, input: "演員高圓圓 將軍霍去病 演員霍思燕", groups: undefined]
複製程式碼

零寬否定先行斷言

肯定是判斷有什麼,否定就是判斷沒有什麼咯。

語法是圓括號內最左邊加上?!標識。

'TypeScript Perl JavaScript'.match(/\b\w{4}(?!Script\b)/);
// ["Perl", index: 11, input: "TypeScript Perl JavaScript", groups: undefined]
複製程式碼

零寬否定後行斷言

語法是圓括號最左邊加上?<!標識。

'演員高圓圓 將軍霍去病 演員霍思燕'.match(/(?<!演員)霍\S+/);
// ["霍去病", index: 8, input: "演員高圓圓 將軍霍去病 演員霍思燕", groups: undefined]
複製程式碼

修飾符

正規表示式除了主體語法,還有若干可選的模式修飾符。

寫法就是將修飾符安插在正則主體的尾巴上。比如這樣:/abc/gi

g修飾符

gglobal的縮寫。預設情況下,正則從左向右匹配,只要匹配到了結果就會收工。g修飾符會開啟全域性匹配模式,找到所有匹配的結果。

'演員高圓圓 將軍霍去病 演員霍思燕'.match(/(?<=演員)\S+/);
// ["高圓圓", index: 2, input: "演員高圓圓 將軍霍去病 演員霍思燕", groups: undefined]
'演員高圓圓 將軍霍去病 演員霍思燕'.match(/(?<=演員)\S+/g);
// ["高圓圓", "霍思燕"]
複製程式碼

i修飾符

iignoreCase的縮寫。預設情況下,/z/是無法匹配Z的,所以我們有時候不得不這樣寫:/[a-zA-Z]/i修飾符可以全域性忽略大小寫。

很多時候我們不在乎文字是大寫、小寫還是大小寫混寫,這個修飾符還是很有用的。

'javascript is great'.match(/JavaScript/);
// null
'javascript is great'.match(/JavaScript/i);
// ["javascript", index: 0, input: "javascript is great", groups: undefined]
複製程式碼

m修飾符

mmultiline的縮寫。這個修飾符有特定起作用的場景:它要和^$搭配起來使用。預設情況下,^$匹配的是文字的開始和結束,加上m修飾符,它們的含義就變成了行的開始和結束。

`
abc
xyz
`.match(/xyz/);
// ["xyz", index: 5, input: "↵abc↵xyz↵", groups: undefined]
`
abc
xyz
`.match(/^xyz$/);
// null
`
abc
xyz
`.match(/^xyz$/m);
// ["xyz", index: 5, input: "↵abc↵xyz↵", groups: undefined]
複製程式碼

y修飾符

這是ES2015的新特性。

ysticky的縮寫。y修飾符有和g修飾符重合的功能,它們都是全域性匹配。所以重點在sticky上,怎麼理解這個粘連呢?

g修飾符不挑食,匹配完一個接著匹配下一個,對於文字的位置沒有要求。但是y修飾符要求必須從文字的開始實施匹配,因為它會開啟全域性匹配,匹配到的文字的下一個字元就是下一次文字的開始。這就是所謂的粘連。

'a bag with a tag has a mag'.match(/\wag/g);
// ["bag", "tag", "mag"]
'a bag with a tag has a mag'.match(/\wag/y);
// null
'bagtagmag'.match(/\wag/y);
// ["bag", index: 0, input: "bagtagmag", groups: undefined]
'bagtagmag'.match(/\wag/gy);
// ["bag", "tag", "mag"]
複製程式碼

有人肯定發現了貓膩:你不是說y修飾符是全域性匹配麼?看上面的例子,單獨一個y修飾符用match方法怎麼並不是全域性匹配呢?

誒,這裡說來就話長了。

長話短說呢,就涉及到y修飾符的本質是什麼。它的本質有二:

  • 全域性匹配(先彆著急打我)。
  • 從文字的lastIndex位置開始新的匹配。lastIndex是什麼?它是正規表示式的一個屬性,如果是全域性匹配,它用來標註下一次匹配的起始點。這才是粘連的本質所在。

不知道你們發現什麼了沒有:lastIndex是正規表示式的一個屬性。而上面例子中的match方法是作用在字串上的,都沒有lastIndex屬性,休怪人家工作不上心。

const reg = /\wag/y;
reg.exec('bagtagmag');
// ["bag", index: 0, input: "bagtagmag", groups: undefined]
reg.exec('bagtagmag');
// ["tag", index: 3, input: "bagtagmag", groups: undefined]
reg.exec('bagtagmag');
// ["mag", index: 6, input: "bagtagmag", groups: undefined]
複製程式碼

我們們換成正則方法exec,多次執行,正則的lastIndex在變,匹配的結果也在變。全域性匹配無疑了吧。

s修飾符

這是ES2018的新特性。

s不是dotAll的縮寫。s修飾符要和.搭配使用,預設情況下,.匹配除了換行符之外的任意單個字元,然而它還沒有強大到無所不能的地步,所以正則索性給它開個掛。

s修飾符的作用就是讓.可以匹配任意單個字元。

ssingleline的縮寫。

`
abc
xyz
`.match(/c.x/);
// null
`
abc
xyz
`.match(/c.x/s);
// ["c↵x", index: 3, input: "↵abc↵xyz↵", groups: undefined]
複製程式碼

u修飾符

這是ES2015的新特性。

uunicode的縮寫。有一些Unicode字元超過一個位元組,正則就無法正確的識別它們。u修飾符就是用來處理這些不常見的情況的。

'?'.match(/^.$/);
// null
'?'.match(/^.$/u);
// ["?", index: 0, input: "?", groups: undefined]
複製程式碼

?,與同義。

筆者對Unicode認識尚淺,這裡不過多展開。


本文是『horseshoe·Regex專題』系列文章之一,後續會有更多專題推出

GitHub地址:github.com/veedrin/hor…

部落格地址(文章排版真的很漂亮):matiji.cn

如果覺得對你有幫助,歡迎來GitHub點Star或者來我的部落格親口告訴我

Regex專題一覽

? 語法

? 方法

? 引擎

相關文章