[JS] 正規運算式 (regular expression)

此文章摘錄自’JavaScript優良部分’一書,純粹個人備忘之用。如欲看全文,請參閱該書。

先來個比對URL的正規式吧:

var parse_url = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;

var url = "http://www.ora.com:80/goodparts?q#fragment";

//讓我們呼叫parse_url的exec方法。如果我們傳入的字串在其中比對成功,則回傳擷取自url的零碎字串的陣列:
var result = parse_url.exec(url);

var names = ['url', 'scheme', 'slash', 'host', 'port', 'path', 'query', 'hash'];

var blanks = '     ';
var i;

for(i = 0; i < names.length; i += 1){
  document.writeln(names[i] + ':' + blanks.substring(names[i].length), result[i]);
}

// 產生結果如下:
// url:     http://www.ora.com:80/goodparts?q#fragment
// scheme:  http
// slash:   //
// host:    www.ora.com
// port:    80
// path:    goodparts
// query:   q
// hash:    fragment

以上解析:

^

^:此字元指出字串的開始。它是個定位點,此例來說,是要阻止exec跳過不像URL的字首。

(?:([A-Za-z]+):)?

此段落比對scheme名稱,但名稱後必須接著:。
(?:…):表示本段落為非記憶集結。(通常,使用非記憶集結,比使用較美觀的記憶集結更好,因為記憶會有效能懲罰。)
(…):代表一個記憶集結
字尾詞?表示該集結為選用集結。

(\/{0,3})

此記憶集結。
\/表示應該比對是否有/(斜線)字元。它被\(反斜線)字元轉義。所以不會被翻譯為正規式實字的結尾。
字尾{0,3}意指它前面的/可以出現0到3次。

([0-9.\-A-Za-z]+)

也是一個記憶集結。用於比對主機名稱(host):由一或多個英數字元,加上「.」或「」字元組成。
需做轉義,表示為\-,以免被誤解為表示範圍的連字號。

(?::(\d+))?

非記憶連結。選用的埠(port)比對,一串接在:字元後的數字序列(一或多個數字)。
\d表示數字字元。

(?:\/([^?#]*))?

開端是/字元。
字元類組[^?#]的起始為^,代表類組包含所有字元,,但?與#除外。
*則表示前面的字元類組應出現零或多次。

(?:\?([^#]*))?

負責比對零或多個字元(不包括#)。

(?:#(.*))?

最後一個選用集結,以#起始。
.」用於比對出任何字元,但行末字元除外。

$

$表示字串的結尾。確保在URL的結尾後,沒有其他多餘的材料。

來看另一個例子:

var parse_number = /^-?\d+(?:\.\d)?(?:e[+\-]?\d+)?$/i;

var test = function(num){
  document.writeln(parse_number.test(num));
];

test('1');              // true
test('number');         // false
test('98.6');           // true
test('132.21.86.100');  // false
test('123.45e-67');     // true
test('123.45D-67');     // false

以下解析:

/^…$/i

再度看到標示正規式的定位點^$,用於要求字串中所有字元,都要與正規式比對。
最後的旗標i,使得找到相符文字時,忽略大小寫的差異。樣式中唯一一個字元就是e;但我們希望e也能找出E。把e這個部分寫成[Ee](?:E|e)也能達成相同效果,但使用了旗標i後,就沒有這種需要了。

-?

負號()後的字尾?,表示負號為選用

\d+

\d[0-9]的意思相同,用於比對數字。字尾+則要求前面的數字需出現一或多次。

(?:\.\d*)?

(?:…)表示一個選用的非記憶集結。本第的集結將比對出浮點數(.後接零或多個數字)。

(?:e[+\-]?\d+)?

再來又是另一個選用的非記憶集結。可比對出e或(E),加上選用的正負號,以及一或多個數字。

建構

有兩種製作RegExp的方式,較建議的是採用正規式實字

一、採用正規式實字

由一對斜線(/)圍起。使用時可能有點麻煩,因為斜線既是除法的字算字,也會用於註解。

對RegExp實用可設置三個旗標,分別以g、i、m代表,旗標直接附加在RegExp實字後:

// 製作比對JavaScript字串的正規式物件
var my_regexp = /"(?:\\.|[^\\\"])*"/g;
旗標 說明
g 全域(比對多次;其精確意義隨不同方法而改變)
i 鈍感(忽略字元的大小寫)
m 多行(^與$能比對行末字元)

二、採用RegExp建構式

此建構式接受傳入的字串,並編譯成RegExp物件,建立字串時需要額外注意,因為在正規式與字串實字裡,反斜線(\)各自具有不意義。通常需要設計成兩條反斜線,並為引號轉義:

// 製作比對JavaScript字串的正規式物件
var my_regexp = new RegExp("\"(?:\\.|[^\\\\\\\"])*\"", 'g');

第二個參數,乃是指定旗標(本例為g)的字串。

特性 使用說明
global 如果使用旗標g時則為true
ignoreCase 如果使用旗標i則為true
lastIndex 開始下一輪exec比對的索引。初始值為0。
multiline 如果使用旗標m時則為true。
source 正規式的來源文字。

零件

正規式選項

正規式選項包含一或多個正規式序列。序列間以|字元分隔。如果任何序列比對出結果,表示選擇項比對成功。

// 從into中比對出in後,不會再找出int;因為in的比對已經成功了。
"into".match(/in|int/)

正規式序列

正規式序列包點一或多個正規式要素,每個正規式要素都能選用性地後接量詞,量語決定正規式要素可出現的次數。如果沒有量詞,素示該部分只需比對出一次。

正規式要素

\ / [ ] ( ) { } ? + * | . ^ $

如果想比對出上述特殊字元,必須加上\來轉譯

未轉譯的.可用於比對任何字元,但行末字元除外。
未轉義的^可用於比對字串的起始處,但lastIndex特性需為0。若指定了旗標m,亦可把行末字元納入比對。

未轉義的$可用於比對字串的結尾處。若指定了旗標m,亦可把行末字元納入比對。

正規式轉義

\f:與在字串裡一樣,代表換頁字元;
\n:則是換行字元;
\r:是游標歸位字元(又稱回列首);
\t:是tab字元;
\u:則能用16位元的十六進位常數,來指定Unicode字元;
\b:在正規式要素中,\b並非代表倒退字元。

\d:與[0-9]的意思相同,用於比對數字;
\D:則是前者的反義,[^0-9]比對非數字字元;
\s:是Unicode空白字元的一部分;
\S:則是前者的反義;
\w:與[0-9A-Z_a-z]意思相同,能比對出所有英數字元;
\W:則是前者的反義,與[^0-9A-Z_a-z]相同;
\1:參照到被第一個集結所記憶的文字,讓它可以再被比對,\2、\3…依此類推。

正規式集結

共有四種集結:

記憶集結:記憶集結是種以括號圍起的正規式選項。符合集結的字元將被記憶。每個記憶集結都有一個編號。正規式裡第一個(,是第一號集結;第二個(是第二號集結。

非記憶集結:非記憶集結多了(?字首。非記憶集結只單純做比對,不記憶符合的文段。因此具有效能稍快的優勢。非記憶集結不會干擾記憶集結的編號。

右合樣集結:右合樣集結多了(?=字首。它與非記憶集結有點像,但在比對出符合集結的文段後,又再回到集結開始的地方,很有效地比對不出任何東西。它不是個優良的部分。

右不合樣集結:右不合樣集結多了(?!字首。它與右合樣集結有點像,但只在找不到比對項目時成功。它也不是個優良的部分

正規式量詞

以大括號圍起的數字,表示正規式要素應該比對的次數。所以:
/www//w{3}/同義;
{3, 6}將用於比對3、4、5、6次;
{3,}可讓比對進行三次以上(含3次)。
?{0, 1}的效果相同;
*{0,}的效果相同;
+{1,}的效果相同。