在本文中,我們將深入探討各種使用正規表示式的方法。
str.match(regexp)
方法 str.match(regexp)
在字串 str
中尋找與 regexp
相符的項目。
它有 3 種模式
-
如果
regexp
沒有旗標g
,則它會傳回第一個相符項目,並以陣列形式顯示,其中包含擷取群組和屬性index
(相符項目的位置)、input
(輸入字串,等於str
)let str = "I love JavaScript"; let result = str.match(/Java(Script)/); alert( result[0] ); // JavaScript (full match) alert( result[1] ); // Script (first capturing group) alert( result.length ); // 2 // Additional information: alert( result.index ); // 7 (match position) alert( result.input ); // I love JavaScript (source string)
-
如果
regexp
有旗標g
,它會傳回所有匹配項的陣列,為字串,不包含擷取群組和其他詳細資料。let str = "I love JavaScript"; let result = str.match(/Java(Script)/g); alert( result[0] ); // JavaScript alert( result.length ); // 1
-
如果沒有匹配項,不論是否有旗標
g
,會傳回null
。這是個重要的細微差別。如果沒有匹配項,我們不會取得一個空陣列,而是
null
。很容易忘記這點而犯錯,例如:let str = "I love JavaScript"; let result = str.match(/HTML/); alert(result); // null alert(result.length); // Error: Cannot read property 'length' of null
如果我們希望結果是一個陣列,我們可以這樣寫
let result = str.match(regexp) || [];
str.matchAll(regexp)
方法 str.matchAll(regexp)
是 str.match
的「較新、較好的」變體。
它主要用於搜尋所有匹配項和所有群組。
它與 match
有 3 個不同之處
- 它傳回一個包含匹配項的可迭代物件,而不是陣列。我們可以使用
Array.from
從中建立一個常規陣列。 - 每個匹配項都傳回一個包含擷取群組的陣列(格式與沒有旗標
g
的str.match
相同)。 - 如果沒有結果,它會傳回一個空的迭代物件,而不是
null
。
使用範例
let str = '<h1>Hello, world!</h1>';
let regexp = /<(.*?)>/g;
let matchAll = str.matchAll(regexp);
alert(matchAll); // [object RegExp String Iterator], not array, but an iterable
matchAll = Array.from(matchAll); // array now
let firstMatch = matchAll[0];
alert( firstMatch[0] ); // <h1>
alert( firstMatch[1] ); // h1
alert( firstMatch.index ); // 0
alert( firstMatch.input ); // <h1>Hello, world!</h1>
如果我們使用 for..of
來迴圈 matchAll
匹配項,我們就不再需要 Array.from
。
str.split(regexp|substr, limit)
使用 regexp(或子字串)作為分隔符號來分割字串。
我們可以使用 split
來處理字串,如下所示
alert('12-34-56'.split('-')) // array of ['12', '34', '56']
但是我們也可以使用正規表示式來分割,方法相同
alert('12, 34, 56'.split(/,\s*/)) // array of ['12', '34', '56']
str.search(regexp)
方法 str.search(regexp)
傳回第一個匹配項的位置,如果找不到,則傳回 -1
let str = "A drop of ink may make a million think";
alert( str.search( /ink/i ) ); // 10 (first match position)
重要的限制:search
只會找到第一個匹配項。
如果我們需要後續匹配項的位置,我們應該使用其他方式,例如使用 str.matchAll(regexp)
來找出所有匹配項。
str.replace(str|regexp, str|func)
這是一個用於搜尋和取代的通用方法,是最有用的方法之一。搜尋和取代的瑞士刀。
我們可以在沒有正規表示式的情況下使用它來搜尋和取代子字串
// replace a dash by a colon
alert('12-34-56'.replace("-", ":")) // 12:34-56
不過有一個陷阱。
當 replace
的第一個引數是字串時,它只會取代第一個匹配項。
您可以在上面的範例中看到:只有第一個 "-"
被 ":"
取代。
要找出所有連字號,我們需要使用正規表示式 /-/g
,而不是字串 "-"
,並加上必要的 g
旗標
// replace all dashes by a colon
alert( '12-34-56'.replace( /-/g, ":" ) ) // 12:34:56
第二個引數是一個取代字串。我們可以在其中使用特殊字元
符號 | 替換字串中的動作 |
---|---|
$& |
插入整個匹配 |
$` |
插入匹配前的部分字串 |
$' |
插入匹配後的部份字串 |
$n |
如果n 是一個 1-2 位數字,插入第 n 個擷取群組的內容,詳細資訊請參閱 擷取群組 |
$<name> |
插入具有給定name 的括號內容,詳細資訊請參閱 擷取群組 |
$$ |
插入字元 $ |
例如
let str = "John Smith";
// swap first and last name
alert(str.replace(/(john) (smith)/i, '$2, $1')) // Smith, John
對於需要「智慧型」替換的情況,第二個引數可以是函式。
它將針對每個匹配呼叫,而傳回值將插入為替換。
函式以引數 func(match, p1, p2, ..., pn, offset, input, groups)
呼叫
match
– 匹配p1, p2, ..., pn
– 擷取群組的內容(如果有的話)offset
– 匹配的位置input
– 來源字串groups
– 具有命名群組的物件。
如果正規表示式中沒有括號,則只有 3 個引數:func(str, offset, input)
。
例如,讓我們將所有匹配轉換為大寫
let str = "html and css";
let result = str.replace(/html|css/gi, str => str.toUpperCase());
alert(result); // HTML and CSS
將每個匹配替換為其在字串中的位置
alert("Ho-Ho-ho".replace(/ho/gi, (match, offset) => offset)); // 0-3-6
在下面的範例中有兩個括號,因此替換函式以 5 個引數呼叫:第一個是完整匹配,然後是 2 個括號,然後是(範例中未使用)匹配位置和來源字串
let str = "John Smith";
let result = str.replace(/(\w+) (\w+)/, (match, name, surname) => `${surname}, ${name}`);
alert(result); // Smith, John
如果有很多群組,可以使用 rest 參數來存取它們
let str = "John Smith";
let result = str.replace(/(\w+) (\w+)/, (...match) => `${match[2]}, ${match[1]}`);
alert(result); // Smith, John
或者,如果我們使用命名群組,則具有它們的groups
物件始終是最後一個,因此我們可以像這樣取得它
let str = "John Smith";
let result = str.replace(/(?<name>\w+) (?<surname>\w+)/, (...match) => {
let groups = match.pop();
return `${groups.surname}, ${groups.name}`;
});
alert(result); // Smith, John
使用函式可以提供我們最終的替換功能,因為它可以取得有關匹配的所有資訊,可以存取外部變數,並且可以執行所有操作。
str.replaceAll(str|regexp, str|func)
此方法基本上與 str.replace
相同,但有兩個主要差異
- 如果第一個引數是字串,它會替換字串的所有出現,而
replace
只會替換第一次出現。 - 如果第一個參數是一個沒有
g
標記的正規表示式,會出現錯誤。加上g
標記後,它的作用與replace
相同。
replaceAll
的主要用途是替換字串中的所有出現。
就像這樣
// replace all dashes by a colon
alert('12-34-56'.replaceAll("-", ":")) // 12:34:56
regexp.exec(str)
regexp.exec(str)
方法會在字串 str
中傳回與 regexp
相符的項目。與前述方法不同,它是針對正規表示式呼叫,而不是針對字串。
它的行為會根據正規表示式是否有 g
標記而有所不同。
如果沒有 g
,則 regexp.exec(str)
會傳回第一個相符項目,就像 str.match(regexp)
一樣。這種行為沒有帶來任何新功能。
但如果有 g
標記,則
- 呼叫
regexp.exec(str)
會傳回第一個相符項目,並在regexp.lastIndex
屬性中儲存其後面的位置。 - 下一個這樣的呼叫會從位置
regexp.lastIndex
開始搜尋,傳回下一個相符項目,並在regexp.lastIndex
中儲存其後面的位置。 - …以此類推。
- 如果沒有相符項目,
regexp.exec
會傳回null
,並將regexp.lastIndex
重設為0
。
因此,重複呼叫會使用 regexp.lastIndex
屬性來追蹤目前的搜尋位置,一個接一個傳回所有相符項目。
過去,在 JavaScript 加入 str.matchAll
方法之前,會在迴圈中呼叫 regexp.exec
來取得所有包含群組的相符項目
let str = 'More about JavaScript at https://javascriptinfo.dev.org.tw';
let regexp = /javascript/ig;
let result;
while (result = regexp.exec(str)) {
alert( `Found ${result[0]} at position ${result.index}` );
// Found JavaScript at position 11, then
// Found javascript at position 33
}
現在也能這樣做,不過對於較新的瀏覽器,str.matchAll
通常比較方便。
我們可以使用 regexp.exec
手動設定 lastIndex
,從特定位置開始搜尋。
例如
let str = 'Hello, world!';
let regexp = /\w+/g; // without flag "g", lastIndex property is ignored
regexp.lastIndex = 5; // search from 5th position (from the comma)
alert( regexp.exec(str) ); // world
如果正規表示式有 y
標記,則搜尋會在位置 regexp.lastIndex
進行,不會再繼續。
讓我們在上面的範例中將 g
標記替換為 y
。由於在位置 5
沒有任何字詞,因此不會有任何相符項目
let str = 'Hello, world!';
let regexp = /\w+/y;
regexp.lastIndex = 5; // search exactly at position 5
alert( regexp.exec(str) ); // null
當我們需要使用正規表示式在特定位置「讀取」字串中的內容,而不是在其他位置時,這會很方便。
regexp.test(str)
方法 regexp.test(str)
尋找比對,並回傳 true/false
表示是否存在。
例如
let str = "I love JavaScript";
// these two tests do the same
alert( /love/i.test(str) ); // true
alert( str.search(/love/i) != -1 ); // true
一個有負面答案的範例
let str = "Bla-bla-bla";
alert( /love/i.test(str) ); // false
alert( str.search(/love/i) != -1 ); // false
如果 regexp 有旗標 g
,則 regexp.test
會從 regexp.lastIndex
屬性開始尋找,並更新此屬性,就像 regexp.exec
一樣。
因此我們可以使用它從特定位置開始搜尋
let regexp = /love/gi;
let str = "I love JavaScript";
// start the search from position 10:
regexp.lastIndex = 10;
alert( regexp.test(str) ); // false (no match)
如果我們將相同的全域 regexp 套用在不同的輸入上,可能會導致錯誤的結果,因為 regexp.test
的呼叫會推進 regexp.lastIndex
屬性,因此在另一個字串中的搜尋可能會從非零位置開始。
例如,我們在同一段文字上呼叫 regexp.test
兩次,第二次會失敗
let regexp = /javascript/g; // (regexp just created: regexp.lastIndex=0)
alert( regexp.test("javascript") ); // true (regexp.lastIndex=10 now)
alert( regexp.test("javascript") ); // false
這正是因為在第二次測試中 regexp.lastIndex
為非零。
為了解決這個問題,我們可以在每次搜尋之前設定 regexp.lastIndex = 0
。或者,不要在 regexp 上呼叫方法,而是使用字串方法 str.match/search/...
,它們不會使用 lastIndex
。
留言
<code>
標籤,對於多行,請將它們包在<pre>
標籤中,對於超過 10 行,請使用沙盒 (plnkr、jsbin、codepen…)