1. 程式人生 > >談談 JavaScript 的正則表示式

談談 JavaScript 的正則表示式

一、背景


最近在做 CMS 系統中不同身份登入使用者的許可權管理,涉及到對 api 路徑的識別去判斷是否放行。以前對正則表示式都是敬而遠之,要用到的話都是直接複製貼上現成網上的表示式,看也看不太懂,借這次機會熟悉下,不求鑽的多深,但求有個整體的認知,滿足我目前的簡單需求即可。

二、介紹


正則表示式(Regular Expression)是一種用來匹配字串的強有力的工具。

各個程式語言對正則表示式的支援標準有所差異,這裡只以 JavaScript 為例。

三、用法


本文對 非捕獲括號、正向肯定查詢、正向否定查詢 不做介紹。

1、匹配符

匹配符 可以匹配
Iamstring Iamstring
. 一個任意字元
\d 一個數字
\D 一個非數字
[a-zA-Z] 一個字母
[^a-zA-Z] 一個非字母
\w 一個字母、數字或者下劃線
[0-9a-zA-Z_] 同上
\s 一個空格(還包括製表符、換頁符和換行符)
[\u4e00-\u9fa5] 中文
A|B A 或 B
[ABC] A 或 B 或 C
[A-C] 同上

2、特殊符號

上面的匹配符可以搭配下表的符號,如 /[A-Z]{3}/

如果遇到歧義可使用括號 (),如/bo+//(bo)+/表達的意思不一樣。

特殊符號 表示 放匹配符的前面 放匹配符的後面 備註
{n} n個字元
{n,m} n-m個字元
* 0或多個字元 等價於{0,}
+ 至少一個字元 等價於{1,}
? 0或1個字元 等價於{0,1}
^ 表示行的開頭
$ 表示行的結束
\b 表示單詞的邊界
\B 表示單詞的非邊界

如果上述特殊符號是普通字元的話,需要用\轉義,如[A-Z]?{3}不對而[A-Z]\?{3}可以正確匹配上 "D???"。

3、貪婪模式和懶惰模式

(1)如何設定?

JavaScript 的正則表示式預設為貪婪模式(匹配儘量多的字元)。

但如果在 *、 +、? 或 {} 的後面出現?,將會變為非貪婪的懶惰模式(匹配儘量少的字元)。

例如,對 "123abc" 應用 /\d+/ 將會返回 "123",如果使用 /\d+?/,那麼就只會匹配到 "1"。

(2)有何區別?

上圖裡有個術語叫回溯,會影響到正則匹配字串的效能,在平時寫正則的時候需要注意,詳細請看下文的三、效能問題

注:java 的正則裡還有一種模式叫獨佔模式,貌似 JS 裡沒有(待求證)。

4、JavaScript 裡使用

(1)建立和 function
// *** 建立正則表示式 ***

// 方法一:/正則表示式主體/修飾符(可選)
var reg1 = /\d/;
// 若 RegExp 是全域性模式
// var reg1 = /\d/g;

// 方法二:new RegExp('正則表示式')
// 注意:不用新增/***/,且由於是字串,所以需要考慮轉義!
// var reg2 = new RegExp("\\d");
// 若 RegExp 是全域性模式
// var reg2 = new RegExp("\\d",'g');

var string = '你好 123 123 js'

// *** RegExp 的方法 - test() exec() *** 
console.log(reg1.test(string));  // true or false

console.log(reg1.exec(string));  // [ '1', index: 3, input: '你好 123 123 js' ]
console.log(reg1.exec(string));  // [ '1', index: 3, input: '你好 123 123 js' ] (跟上面一樣)
// 若 RegExp 是全域性模式
console.log(reg1.exec(string));  // [ '1', index: 3, input: '你好 123 123 js' ]  
console.log(reg1.exec(string));  // [ '2', index: 4, input: '你好 123 123 js' ]
console.log(reg1.exec(string));  // [ '3', index: 5, input: '你好 123 123 js' ]
console.log(reg1.exec(string));  // [ '1', index: 7, input: '你好 123 123 js' ]
console.log(reg1.exec(string));  // [ '2', index: 8, input: '你好 123 123 js' ]
console.log(reg1.exec(string));  // [ '3', index: 9, input: '你好 123 123 js' ]
console.log(reg1.exec(string));  // null
console.log(reg1.exec(string));  // [[ '1', index: 3, input: '你好 123 123 js' ]

// *** String 的方法 - search() match() replace() split()  *** 
console.log(string.search(reg1));   // 返回第一個匹配的下標,沒有即是 -1 

console.log(string.match(reg1));    // [ '1', index: 3, input: '你好 123 123 js' ] (跟RegExp.exec()一樣)
// 若 RegExp 是全域性模式
console.log(string.match(reg1));    // [ '1', '2', '3', '1', '2', '3' ]

console.log(string.replace(reg1,'替換'));   // "你好 替換23 123 js" 
// 若 RegExp 是全域性模式
console.log(string.replace(reg1,'$2-$1'));   // "你好 替換替換替換 替換替換替換 js" 
 
console.log(string.split(reg1));    // [ '你好 ', '', '', ' ', '', '', ' js' ]

除了g表示全域性模式,還有i表示忽略大小寫,m表示執行多行匹配。

(2)分組

捕獲括號() 可以分組,受影響的是 match()、split()、replace() 方法。

var string = '010-12345'
// 無()
var reg1 = /^\d{3}-\d{3,8}$/;
// 有()
var reg2 = /^(\d{3})-(\d{3,8})$/;

// match()
console.log(string.match(reg1));    
// [ '010-12345', index: 0, input: '010-12345' ]
console.log(string.match(reg2));    
// [ '010-12345', '010', '12345', index: 0, input: '010-12345' ]

// split()
console.log(string.split(reg1));    
// [ '', '' ]
console.log(string.split(reg2));    
// [ '', '010', '12345', '' ]

// replace() - 注意 $1、$2 的含義
console.log(string.replace(reg1,'替換'));
console.log(string.replace(reg2,'$2-$1'));  // 12345-010

三、效能問題


首先,實現正則表示式引擎有兩種方式:DFA 自動機(Deterministic Final Automata 確定型有窮自動機)和 NFA 自動機(Non deterministic Finite Automaton 不確定型有窮自動機)。

對於這兩種自動機,他們有各自的區別,這裡並不打算深入將它們的原理。簡單地說,DFA 自動機的時間複雜度是線性的,更加簡單穩定,但是功能有限。而 NFA 的時間複雜度比較不穩定,有時候很好,有時候不怎麼好,好不好取決於你寫的正則表示式,但是勝在功能更加強大。

JS 與大多數主流語言的正則引擎選用的都是 NFA。

但需要注意的是,這種正則表示式引擎在進行字元匹配時會發生回溯(backtracking)。而一旦發生回溯,那其消耗的時間就會變得很長,有可能是幾分鐘,也有可能是幾個小時,時間長短取決於回溯的次數和複雜度。

這裡有兩篇關於 JAVA 中遭遇到由於正則表示式的糟糕效能導致上線後伺服器 CPU 飆到 100% 的血的經驗地分享:

https://zhuanlan.zhihu.com/p/38278481

http://www.cnblogs.com/study-everyday/p/7426862.html

所以,我們更要在平時寫正則表示式的時候,更加注重效能,避免回溯機制帶來的隱患。我們可以用 https://regex101.com/ 來測試下match 的時間如何。

四、推薦工具


1、線上匹配

http://tool.oschina.net/regex

2、檢視正則表示式的具體解析過程

https://regexper.com

五、參考資料:


1、MDN教程

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions

2、廖雪峰教程

https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434499503920bb7b42ff6627420da2ceae4babf6c4f2000