1. 程式人生 > 其它 >從零開始學正則(三)

從零開始學正則(三)

技術標籤:正則正則表示式

故心故心故心故心小故衝啊


文章目錄


從零開始學正則(三),正則表示式括號的作用

不管哪門語言中都有括號。正則表示式也是一門語言,而括號的存在使這門語言更為強大。對括號的使用是否得心應手,是衡量對正則的掌握水平的一個側面標準。括號的作用,其實三言兩語就能說明白,括號提供了分組,便於我們引用它。 引用某個分組,會有兩種情形:在 JavaScript 裡引用它,在正則表示式裡引用它。

內容包括:
分組和分支結構
分組引用
反向引用
相關案例

1.1. 分組和分支結構

這二者是括號最直覺的作用,也是最原始的功能,強調括號內的正則是一個整體,即提供子表示式。

1.1.1. 分組

我們知道 /a+/ 匹配連續出現的 “a”,而要匹配連續出現的 “ab” 時,需要使用 /(ab)+/。
其中括號是提供分組功能,使量詞 + 作用於 “ab” 這個整體,測試如下

var regex = /(ab)+/g;
var string = "ababa abbb ababab";
console.log( string.match(regex) );
// => ["abab", "ab", "ababab"]

1.1.2. 分支結構

而在多選分支結構 (p1|p2) 中,此處括號的作用也是不言而喻的,提供了分支表示式的所有可能。

var regex = /^I love (guxin|guxinxin)$/;
console.log( regex.test("I love guxin") );
console.log( regex.test("I love guxinxin") );
// => true
// => true

1.2. 分組引用

這是括號一個重要的作用,有了它,我們就可以進行資料提取,以及更強大的替換操作。
而要使用它帶來的好處,必須配合使用實現環境的 API。 以日期為例。假設格式是 yyyy-mm-dd 的,我們可以先寫一個簡單的正則:

var regex = /\d{4}-\d{2}-\d{2}/;

在這裡插入圖片描述

然後再修改成括號版的:

var regex = /(\d{4})-(\d{2})-(\d{2})/;

在這裡插入圖片描述

對比這兩個視覺化圖片,我們發現,與前者相比,後者多了分組編號,如 Group #1。 其實正則引擎也是這麼做的,在匹配過程中,給每一個分組都開闢一個空間,用來儲存每一個分組匹配到的
資料。 既然分組可以捕獲資料,那麼我們就可以使用它們。

1.2.1. 提取資料

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
console.log( string.match(regex) );
// => ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"]

match 返回的一個數組,第一個元素是整體匹配結果,然後是各個分組(括號裡)匹配的
內容,然後是匹配下標,最後是輸入的文字。另外,正則表示式是否有修飾符 g,match
返回的陣列格式是不一樣的。

另外也可以使用正則例項物件的 exec 方法:

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
console.log( regex.exec(string) );
// => ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"]

同時,也可以使用建構函式的全域性屬性 $1 至 $9 來獲取:

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
regex.test(string); // 正則操作即可,例如
//regex.exec(string);
//string.match(regex);
console.log(RegExp.$1); // "2017"
console.log(RegExp.$2); // "06"
console.log(RegExp.$3); // "12"

1.2.2. 替換

比如,想把 yyyy-mm-dd 格式,替換成 mm/dd/yyyy 怎麼做?

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, "$2/$3/$1");
console.log(result);
// => "06/12/2017"

其中 replace 中的,第二個引數裡用 $1、$2、$3 指代相應的分組。等價於如下的形式:

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, function () {
  return RegExp.$2 + "/" + RegExp.$3 + "/" + RegExp.$1;
});
console.log(result);
// => "06/12/2017"

也等價於:

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, function (match, year, month, day) {
  return month + "/" + day + "/" + year;
});
console.log(result);
// => "06/12/2017

1.3. 反向引用

除了使用相應 API 來引用分組,也可以在正則本身裡引用分組。但只能引用之前出現的分組,即反向引用。
還是以日期為例。
比如要寫一個正則支援匹配如下三種格式:

2016-06-12
2016/06/12
2016.06.12

最先可能想到的正則是:

var regex = /\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // true

其中 / 和 . 需要轉義。雖然匹配了要求的情況,但也匹配 “2016-06/12” 這樣的資料。 假設我們想要求分割符前後一致怎麼辦?此時需要使用反向引用:

var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // false

注意裡面的 \1,表示的引用之前的那個分組 (-|/|.)。
不管它匹配到什麼(比如 -),\1 都匹配那個同 樣的具體某個字元。 我們知道了 \1 的含義後,那麼 \2 和 \3 的概念也就理解了,即分別指代第二個和第三個分組。

1.4. 相關案例

1.字串 trim 方法模擬

trim 方法是去掉字串的開頭和結尾的空白符。有兩種思路去做。 第一種,匹配到開頭和結尾的空白符,然後替換成空字元。如:

function trim(str) {
  return str.replace(/^\s+|\s+$/g, ''); }
console.log( trim(" foobar ") );
// => "foobar"

第二種,匹配整個字串,然後用引用來提取出相應的資料:

function trim (str) {
  return str.replace(/^\s*(.*?)\s*$/g, "$1"); }
console.log( trim(" foobar ") );
// => "foobar"

這裡使用了惰性匹配 *?,不然也會匹配最後一個空格之前的所有空格的。
當然,前者效率高