《JavaScript Ninja》之正則表達式

正則表達式 是一個拆分字符串並查詢相關信息的過程。javascript

練習網站:JS Binhtml

正則表達式測試網站:Regular Expression Test Page for JavaScriptjava

正則表達式進修正則表達式

正則表達式 一般被稱爲 模式,是一個用簡單方式描述或匹配一系列符合某個句法規則的字符串。express

建立正則表達式的兩種方法:

(1)經過正則表達式字面量(推薦);編程

var pattern = /test/;

就像字符串是用引號進行界定同樣,正則字面量是用 正斜槓 進行界定的。數組

(2)經過構造 RegExp 對象的實例。函數

var patter = new RegExp("test");

* 在開發中,若是正則式已知的,則優先選擇字面量語法,而構造器方式則是用於在運行時,經過動態構建字符串來構建正則表達式。測試

正則表達式的3個標誌——>這些標誌將附加到字面量尾部:/test/ig,或者做爲 RegExp 構造器的第二個字符參數:new RegExp("test","ig")優化

i:不區分大小寫
g:匹配模式中的全部實例
m:容許匹配多個行

術語與操做符

精確匹配:若是一個字符不是特殊字符或操做符,則表示該字符必須在表達式中出現。

/test/:匹配字符串 test

匹配一類字符:匹配一個有限字符集中的某一個字符,能夠把字符集放到中括號內,來指定該 字符集(類)操做符

若想匹配一組有限字符集之外的字符,能夠經過在中括號第一個開括號的後面加一個插入符(^)來實現。

字符集的變異操做:制定一個範圍。(-)

[abc]:匹配 "a","b","c" 中的任何一個字符
[^abc]:匹配除了 "a","b","c" 之外的任意字符
[a-m]:匹配 "a" 和 "m" 之間的任何一個小寫字母

轉義:使用反斜槓對任意字符進行轉義,讓被轉義字符做爲字符自己進行匹配。(在字符串中使用時,須要對反斜槓進行轉義)

\[:匹配 [ 字符
\\:匹配 \ 字符

匹配開始或結束:插入符號(^)表示從字符串的開頭進行匹配,美圓符號($)表示該模式必須出如今字符串的結尾

這裏的插入符號只是 ^ 字符的一個重載,它還能夠用於否認一個字符類集。

/^test$/:同時使用^和$,代表指定的模式必須包含整個候選字符串

重複出現:匹配任意數量的相同字符

  • ?:在一個字符後面加一個問號,定義爲該字符是可選的(能夠出現一次或根本不出現)

    /t?est/:能夠匹配 "test" 和 "est"
  • +:若是一個字符要出現一次或屢次,可使用加號

    /t+est/:能夠匹配 "test" "ttest" "tttest"
  • *:若是一個字符要出現0次或屢次,可使用星號

    /t*est/:能夠匹配 "test" "ttest" "tttest" "est"
  • {n}:在字符後面的花括號裏指定一個數字來表示重複次數

    /a{4}/:表示匹配含有連續四個 "a" 字符的字符串
  • {n,m}:在字符後面的花括號裏指定兩個數字(用逗號隔開),表示重複次數區間

    /a{4,10}/:表示匹配任何含有連續4個至10個 "a" 字符的字符串
  • {n,}:次數區間的第二個值是可選的(可是要保留逗號),表示一個開區間

    /a{4,}/:表示匹配任何含有連續4個或多於4個 "a" 字符的字符串

這些重複操做符能夠是 貪婪的 非貪婪的。默認狀況下,它們是 貪婪的:它們匹配全部的字符組合。

在操做符後面加一個問號 ? 字符(? 操做符的一個重載),如 a+?,可讓該表達式編程稱爲 非貪婪的:進行最小限度的匹配。

// 對字符串 "aaa" 進行匹配
/a+/:將匹配全部這三個字符
/a+?/:只匹配一個 a 字符,由於一個 a 字符就能夠知足 a+ 術語

預約義字符類:匹配一些不可能用字面量字符來表示的字符(如像回車這樣的控制字符、小數位數或一組空白字符)

7-1

分組:若是將操做符應用於一組術語,能夠像數學表達式同樣在該組上使用小括號

/(ab)+/:匹配一個或多個連續出現的子字符串 "ab"
  • 當正則表達式有一部分是用括號進行分組時,它具備雙重責任,同時也建立所謂的 捕獲

或操做符(OR):能夠用豎線 ( | ) 表示或者的關係。

/a|b/:匹配 "a" 或 "b" 字符
/(ab)+|(cd)+/:匹配出現一次或屢次的 "ab" 或 "cd"

反向引用:正則表達式中最複雜的術語是,在正則中所定義的 捕獲 的反向引用,將 捕獲 做爲正則表達式中可以成功匹配術語時的候選字符串。

這種術語表示法是在 反斜槓 後面加一個要引用的捕獲數量,該數字從1開始,如\一、\2等。

/^([dtn])a\1/:能夠任意一個以 "d","t","n" 開頭,且後面跟着一個 a 字符,而且再後面跟着的是和第一個捕獲相同字符的字符串

不一樣於 /[dtn]a[dtn]/,a 後面的字符有可能不是 "d" "t" 或 "n" 開頭,但這個字符確定是以觸發該匹配的其中一個字符 ("d" "t" 或 "n") 開頭。所以,\1 匹配的字符須要在執行的時候才能肯定。

編譯正則表達式

正則表達式的兩個重要階段:

(1)編譯:發生在正則表達式第一次被建立的時候;

(2)執行:發生在咱們使用編譯過的正則表達式進行字符串匹配的時候。

/*
 * 建立編譯後正則表達式的兩種方式
 * 正則表達式在建立以後都處於編譯後的狀態
 */
var re1 = /test/i;  // 經過字面量建立

var re2 = new RegExp("test", "i");  // 經過構造器建立

console.log(re1.toString() == "/test/i");   // true
console.log(re1.test("TesT"));  // true
console.log(re2.test("TesT"));  // true
console.log(re1.toString() == re2.toString());  // true
console.log(re1 != re2);    // true
  • 若是將 re1 的引用再替換成 /test/i 字面量,那麼同一個正則表達式可能又被編譯屢次,因此正則表達式只編譯一次,並將其保存在一個變量中以供後續使用,這是一個重要的優化過程

注意:每一個正則表達式都有一個獨立的對象表示,每次建立正則表達式(也所以被編譯),都會爲此建立一個新的正則表達式對象。和其餘的原始類型不太同樣,其結果將永遠是獨一無二的

特別重要的一點,用 構造器 建立正則表達式的使用,這種技術容許咱們,在運行時經過動態建立的字符串構建和編譯一個正則表達式。對於構建大量重用的複雜表達式來講,這是很是有用的。

[例子:]

<div calss="samurai ninja">a</div>
<div class="ninja samurai">b</div>
<div></div>
<span class="samurai ninja ronin">c</span>

<script>
    function findClassInElements(className, type) {

        var elems = document.getElementsByTagName(type || "*");

        /* 
         * 正則表達式匹配的字符串:
         * 要以空字符串或空格開始,
         * 然後跟着指定樣式名稱,
         * 而且緊隨其後的是一個空白字符或結束字符串
         */
        var regex = new RegExp("(^|\\s)" + className + "(\\s|$)");

        var results = [];

        for (var i = 0, length = elems.length; i < length; i++) {
            if (regex.test(elems[i].className)) {
                results.push(elems[i]);
            }
        }

        return results;
    }

    var results1 = findClassInElements("ninja", "div"); // <div class="ninja samurai">b</div>
    var results2 = findClassInElements("ninja", "span"); // <span class="samurai ninja ronin">c</span>
    var results3 = findClassInElements("ninja");    // <div class="ninja samurai">b</div>
                                                    // <span class="samurai ninja ronin">c</span>

    console.log(results1.length);   // 1
    console.log(results2.length);   // 1
    console.log(results3.length);   // 2
</script>
  • 在該例子的正則中,要注意 反斜槓(\\)的使用:\\s。建立帶有反斜槓字面量正則表達式時,只須要提供一個反斜槓便可。可是,因爲咱們在字符串中寫反斜槓,因此須要 雙反斜槓 進行轉義。要明白,咱們是用字符串(而不是字面量)來構建正則表達式。

用正則表達式進行捕捉操做

正則表達式的 實用性 表如今捕獲已匹配的結果上,這樣咱們即可以在其中進行處理。

1. 執行簡單的捕獲 —— match()
  • match() 返回的數組的第一個索引的值老是該匹配的完整結果,而後是每一個後續捕獲結果。

  • 記住,捕獲是由正則表達式中的小括號所定義。

2. 用全局表達式(/g)進行匹配 —— match() exec()
  • 使用 局部正則表達式 時,返回的該數組包含了在匹配操做中成功匹配的整個字符串以及其餘捕獲結果。

  • 使用 全局正則表達式 時,匹配全部可能的匹配結果,返回的數組包含了全局匹配結果,而不只僅是第一個匹配結果。

使用 match()exec(),咱們老是能夠找到想要尋找的精確匹配。

[例子1:]

// 使用 match() 進行全局搜索和局部搜索時的不一樣
var html = "<div class='test'><b>Hello</b> <i>world!</i></div>";

// 局部正則表達式會返回一個數組,該數組包含了在匹配操做中匹配的整個字符串以及其餘捕獲結果
var results = html.match(/<(\/?)(\w+)([^>]*?)>/);   // 局部正則匹配

console.log(results);   // ["<div class='test'>", "", "div", " class='test'"]
console.log(results[0] == "<div class='test'>");    // true
console.log(results[1] == "");  // true
console.log(results[2] == "div");   // true
console.log(results[3] == " class='test'"); // true

// 全局正則表達式返回一個數組,匹配全部可能的匹配結果,而不只僅是第一個匹配結果
var all = html.match(/<(\/?)(\w+)([^>]*?)>/g);  // 全局正則匹配

console.log(all);   // ["<div class='test'>", "<b>", "</b>", "<i>", "</i>", "</div>"]
console.log(all[0] == "<div class='test'>");    // true
console.log(all[1] == "<b>");   // true
console.log(all[2] == "</b>");  // true
console.log(all[3] == "<i>");   // true
console.log(all[4] == "</i>");  // true
console.log(all[5] == "</div>");    // true

[例子2:]

// 使用 exec() 方法進行捕獲和全局搜索
var html = "<div class='test'><b>Hello</b> <i>world!</i></div>";

var tag = /<(\/?)(\w+)([^>]*?)/g, match;
var num = 0;

while ((match = tag.exec(html)) !== null) {
    console.log(match.length);  // 4
    num++;
}

console.log(num);   // 6
3. 捕獲的引用

兩種方法 ,能夠引用捕獲到的匹配結果:

(1)自身匹配

(2)替換字符串

與反向引用不一樣,經過調用 replace() 方法替換字符串得到捕捉的引用時,使用 $1$2$3 語法表示每一個捕獲的數字

/*
 * 使用反向引用匹配 HTML 標籤內容
 */
var html = "<div class='hello'>Hello</b> <i>world!</i>";

var pattern = /<(\w+)([^>]*)>(.*?)<\/\1>/g; // 使用捕獲的反向引用

var match = pattern.exec(html); // 在測試字符串上進行匹配

console.log(match); // ["<i>world!</i>", "i", "", "world!"]

console.log(match[0] == "<div class='hello'>Hello</b>");    // false
console.log(match[1] == "b");   // false
console.log(match[2] == " class='hello'");  // false
console.log(match[3] == "Hello");   // false

console.log(match[0] == "<i>world!</i>");   // true
console.log(match[1] == "i");   // true
console.log(match[2] == "");    // true
console.log(match[3] == "world!");  // true

/*
 * replace() 方法得到捕捉的引用:使用 $一、$二、$3 語法表示每一個捕獲的數字
 */
"fontFamily".replace(/([A-Z])/g, "-$1").toLowerCase();  // font-family
4. 沒有捕獲的分組

小括號的雙重責任:(1)進行分組;(2)指定捕獲。

  • 因爲捕獲和表達式分組都使用了小括號,因此沒法告訴正則表達式處理引擎,哪一個小括號是用於分組,以及哪一個是用於捕獲的。

  • 因此會將小括號既視爲分組,又視爲捕獲,因爲有時候須要指定一些正則表達式分組,因此會致使捕獲的信息比咱們預期的還要多。

要讓一組括號不進行結果捕獲,正則表達式的語法容許咱們在開始括號後加一個 ?: 標記,這就是所謂的 被動子表達式

var pattern = /((?:ninja-)+)sword/;
// 外層小括號 ——> 定義捕獲(sword以前的字符串)
// 內層小括號 ——> 針對 + 操做符,對 "ninja-" 文本進行分組
// ?: ——> 只會爲外層的括號建立捕獲,內層括號被轉換爲一個被動子表達式

利用函數進行替換

String 對象的 replace 方法:

(1)將正則表達式做爲第一個參數,致使在該模式的匹配元素上進行替換。

"ABCDEfg".replace(/[A-Z]/g, "X");   // XXXXXfg,替換全部的大寫字母爲「X」

(2)(最強大特性)接受一個函數做爲替換值,函數的返回值是即將要替換的值。(這樣能夠在運行時肯定該替換的字符串,並掌握大量與匹配有關的信息。)

/* 
 * 第一個參數:傳入完整的字符串
 * 第二個參數:捕獲結果
 */
function upper(all, letter) {
    return letter.toUpperCase();
}

/* 
 * 函數第一次調用時,傳入 "-b" 和 "b"
 * 函數第二次調用時,傳入 "-w" 和 "w"
 */
"border-bottom-width".replace(/-(\w)/g, upper); // borderBottomWidth

當替換值(第二個參數)是一個函數時,每一個匹配都會調用該函數並帶有一串參數列表。

  • 匹配的完整文本。
  • 匹配的捕獲,一個捕獲對應一個參數。
  • 匹配字符在源字符串中的索引。
  • 源字符串。

[例子:]

/*
 * 讓查詢字符串轉換成另一個符合咱們需求的格式
 * 原始字符串:foo=1&foo=2&blah=a&blah=b&foo=3
 * 轉換成:foo=1,2,3&blah=a,b
 */
function compress(source) {
    var keys = {};  // 保存局部鍵

    source.replace(
        /([^=&]+)=([^&]*)/g,
        function(full, key, value) {
            keys[key] = (keys[key] ? keys[key] + "," : "") + value; // 提取鍵值對信息

            /* 
             * 返回空字符串,由於咱們確實不關注源字符串中發生的替換操做
             * 咱們只須要利用該函數的反作用,而不須要實際替換結果
             */
            return "";
        }
    );

    // 收集鍵的信息
    var result = [];
    for (var key in keys) {
        result.push(key + "=" + keys[key]);
    }

    // 使用&將結果進行合併
    return result.join("&");
}

var origin = "foo=1&foo=2&blah=a&blah=b&foo=3";
compress(origin);   // "foo=1,2,3&blah=a,b"
  • 一個有趣點:如何使用字符串的 replace() 方法來遍歷一個字符串,而不是一個實際的搜索替換機制。

  • 兩個關鍵點:1)傳遞一個函數做爲替換值參數;2)該函數並非返回實際的值,而是簡單地利用它做爲一種搜索手段。

利用這種技巧,咱們可使用 String 對象的 replace() 方法做爲字符串搜索機制。搜索結果不只快速,並且簡單、有效。

經常使用正則表達式

1. 修剪字符串:
// 修剪字符串
function trim(str) {
    return (str || "").replace(/^\s+|\s+$/g, "");
}

trim(" #id div.class ");    // "#id div.class"
2. 匹配換行符:
// 匹配全部的字符,包括換行符
var html = "<b>Hello</b>\n<i>world!</i>";

// 顯示換行符沒有被匹配到
console.log(/.*/.exec(html)[0] === "<b>Hello</b>"); // true

// 使用空白符匹配方式匹配全部的元素,爲最佳方案
console.log(/[\S\s]*/.exec(html)[0] === "<b>Hello</b>\n<i>world!</i>"); // true

// 用另外一種方式匹配全部元素
console.log(/(?:.|\s)*/.exec(html)[0] === "<b>Hello</b>\n<i>world!</i>");   // true
3. Unicode:
// 匹配 Unicode 字符
var text = "\u5FCD\u8005\u30D1\u30EF\u30FC";

var matchAll = /[w\u0080-\uFFFF_-]+/;

text.match(matchAll);   // ["忍者パワー"]
4. 轉義字符:
// 在 CSS 選擇器中匹配轉義字符
var pattern = /^((\w+)|(\\.))+$/;   // 容許匹配一個單詞字符,
                                    // 或一個反斜槓及後面跟隨任意字符(甚至是另一個反斜槓)
                                    // 或者二者均可以匹配
var tests = [
    "formUpdate",   // true
    "form\\.update\\.whatever", // true
    "form\\:update",    // true
    "\\f\\o\\r\\m\\u\\p\\d\\a\\t\\e",   // true
    "form:update"   // false,未能匹配到非單詞字符(:)
];

for (var n=0; n<tests.length; n++) {
    console.log(pattern.test(tests[n]));
}

參考博文:

JavaScript 正則表達式上——基本語法

JavaScript正則表達式下——相關方法

我所認識的JavaScript正則表達式

正則表達式 - 教程

解惑正則表達式中的捕獲

相關文章
相關標籤/搜索