方法篇,JavaScript 中都有哪些操作正則的方法。
RegExp 物件方法
方法 | 描述 |
---|---|
exec | 檢索字串中指定的值。返回找到的值,並確定其位置。 |
test | 檢索字串中指定的值。返回 true 或 false。正則.test(字串) |
regexp.test(str)
方法 regexp.test(str) 查詢匹配項,然後返回 true/false
表示是否存在。
let str = "I love JavaScript";
// 這兩個測試相同
console.log(/love/i.test(str)); // true
console.log(str.search(/love/i) != -1); // true
regexp.exec(str)
regexp.exec(str) 方法返回字串 str 中的 regexp 匹配項, 可指定從位置進行搜尋。
基於否具有修飾符 g 其有兩種搜尋模式。
(1) 沒有修飾符 g,則 regexp.exec(str) 會返回與 第一個匹配項,就像 str.match(regexp) 那樣。這種行為並沒有帶來任何新的東西。
let str = "More about JavaScript at https://javascript.info";
let regexp = /javascript/i;
console.log(regexp.exec(str));
/* [
0 : "JavaScript"
groups:undefined
index: 11
input :"More about JavaScript at https://javascript.info"
] */
(2) 有修飾符 g,可以基於 regexp.lastIndex 位置迴圈搜尋全部。
詳細步驟:
- 呼叫 regexp.exec(str) 會返回第一個匹配項,並將緊隨其後的位置儲存在屬性 regexp.lastIndex 中。
- 下一次這樣的呼叫會從位置 regexp.lastIndex 開始搜尋,返回下一個匹配項,並將其後的位置儲存在 regexp.lastIndex 中。
……以此類推。 - 如果沒有匹配項,則 regexp.exec 返回 null,並將 regexp.lastIndex 重置為 0。
- 因此,重複呼叫會一個接一個地返回所有匹配項,使用屬性 regexp.lastIndex 來跟蹤當前搜尋位置。
過去,在將 str.matchAll 方法新增到 JavaScript 之前,會在迴圈中呼叫 regexp.exec 來獲取組的所有匹配項:
let str = "More about JavaScript at https://javascript.info";
let regexp = /javascript/gi;
let result;
while ((result = regexp.exec(str))) {
console.log(`Found ${result[0]} at position ${result.index}`);
// 在位置 11 找到了 JavaScript,然後
// 在位置 33 找到了 javascript
}
這現在也有效,儘管對於較新的瀏覽器 str.matchAll
通常更方便。
指定位置搜尋
我們可以透過手動設定 lastIndex,用 regexp.exec 從給定位置進行搜尋。
例如:
let str = "Hello, world!";
let regexp = /\w+/g; // 沒有修飾符 "g",lastIndex 屬性會被忽略
regexp.lastIndex = 5; // 從第 5 個位置搜尋(從逗號開始)
console.log(regexp.exec(str)); // world
如果正規表示式帶有修飾符 y,則搜尋將精確地在 regexp.lastIndex 位置執行,不會再進一步。
讓我們將上面示例中的 g 修飾符替換為 y。現在沒有找到匹配項,因為在位置 5 處沒有單詞:
let str = "Hello, world!";
let regexp = /\w+/y;
regexp.lastIndex = 5; // 在位置 5 精確查詢
console.log(regexp.exec(str)); // null
當我們需要透過正規表示式在確切位置而不是其後的某處從字串中“讀取”某些內容時,這很方便。
String 物件的方法
str.match(regexp)
str.match(regexp) 方法在字串 str 中查詢 regexp 的匹配項。搜尋成功就返回內容,格式為陣列,失敗就返回 null。
它有三種模式:
- 非全域性匹配, 不帶 g,返回第一個匹配項,其中包括捕獲組和屬性 index
let str = "2022/10/24";
let result = str.match(/(\d{4})\/(\d{2})\/(\d{2})/);
// 第一份是完全匹配的
console.log(result[0]); // 2022/10/24(完全匹配)
// 捕獲組的結果從第二項開始展示,如果沒有捕獲內容則顯示 undefined
console.log(result[1]); // "2022"(第一個分組)
console.log(result[2]); // "10"(第一個分組)
console.log(result[3]); // "24"(第一個分組)
console.log(result.length); // 4
// 其他屬性:
console.log(result.index); // 0(匹配位置)
console.log(result.input); // '2022/10/24'(源字串)
// groups 屬性它儲存的不是捕獲組的資訊,而是捕獲命名的資訊(自定義捕獲組名時生效)。
console.log(result.groups); // undefined
// 非捕獲組不會捕獲
console.log("2022/10/24".match(/(?:\d{4})\/(\d{2})\/(\d{2})/)); // ['2022/10/24', '10', '24', index: 0, input: '2022/10/24', groups: undefined]
- 全域性匹配 帶有 g,則它將返回一個包含所有匹配項的陣列,但不包含捕獲組和其它詳細資訊
"2022/10/24".match(/(?:\d{4})\/(\d{2})\/(\d{2})/g); // ['2022/10/24']
"2022/10/24".match(/(\d{4})\/(\d{2})\/(\d{2})/g); // ['2022/10/24']
- 沒有匹配項, 則無論是否帶有修飾符 g,都將返回 null。
"2022/10/24".match(/(\d{6})/); // null
let result = "2022/10/24".match(/(\d{6})/) || []; // 希望結果是陣列
console.log(result); // []
str.matchAll(regexp)
注意: 這是一個最近新增到 JavaScript 的特性。 舊式瀏覽器可能需要 polyfills.
方法 str.matchAll(regexp) 是 str.match 的“更新、改進”的變體。
它主要用來搜尋所有組的所有匹配項。
與 match 相比有 3 個區別:
- 它返回一個包含匹配項的可迭代物件,而不是陣列。我們可以用 Array.from 將其轉換為一個常規陣列。
- 每個匹配項均以一個包含捕獲組的陣列形式返回(返回格式與不帶修飾符 g 的 str.match 相同)。
- 如果沒有結果,則返回的是一個空的可迭代物件而不是 null。
let matchAll = "2022/10/24".matchAll(/(\d{4})\/(\d{2})\/(\d{2})/g);
console.log(matchAll); // [object RegExp String Iterator],不是陣列,而是一個可迭代物件
matchAll = Array.from(matchAll); // 現在是陣列了 // [0: ['2022/10/24', '2022', '10', '24', index: 0, input: '2022/10/24', groups: undefined]]
let firstMatch = matchAll[0];
console.log(firstMatch[0]); // '2022/10/24'
console.log(firstMatch[1]); // 2022
console.log(firstMatch[2]); // 10
console.log(firstMatch[3]); // 24
console.log(firstMatch.index); // 0
console.log(firstMatch.input); // 2022/10/24
如果我們用 for..of 來遍歷 matchAll 的匹配項,那麼我們就不需要 Array.from 了。
str.split(regexp|substr, limit)
使用正規表示式(或子字串)作為分隔符來分割字串。
我們可以用 split 來分割字串,像這樣:
const [y, m, d] = "2022/10/24".split("/");
console.log([m, d, y].join("/")); // 10/24/2022
但同樣,我們也可以用正規表示式:
console.log("2022, 10, 24".split(/,\s*/)); // ['2022', '10', '24']
另外,因為 split 方法中的正則是用來匹配分隔符,所以全域性匹配沒有意義。
str.search(regexp)
方法 str.search(regexp) 返回第一個匹配項的位置索引,如果沒找到,則返回 -1:
let str = "A drop of ink may make a million think";
console.log(str.search(/ink/i)); // 10(第一個匹配位置)
重要限制:search 僅查詢第一個匹配項。
如果我們需要其他匹配項的位置,則應使用其他方法,例如用 str.matchAll(regexp) 查詢所有位置。
str.replace(str|regexp, str|func)
這是用於搜尋和替換的通用方法,是最有用的方法之一。它是搜尋和替換字串的瑞士軍刀。
我們可以在不使用正規表示式的情況下使用它來搜尋和替換子字串。
當 replace 的第一個引數是字串時,它只替換第一個匹配項。
在下面的示例中看到:只有第一個 "/" 被替換為了 "-"。
// 用-替換/字元
console.log("2022/10/24".replace("/", "-")); // 2022-10/24
// 類似於非全域性模式的正則匹配
console.log("2022/10/24".replace(/\//, "-")); // 2022-10/24
如要找到所有的連字元,我們不應該用字串 "/",而應使用帶 g 修飾符的正規表示式 /\//g
:
// 將所有用-替換/字元
console.log("2022/10/24".replace(/\//g, "-")); // 2022-10-24
第二個引數是替換字串。我們可以在其中使用特殊字元, 在實際替換時 replace 內部邏輯會自動解析字串,提取出變數。
符號 | 替換字串中的行為 |
---|---|
$& | 插入整個匹配項 |
$` | 插入字串中匹配項之前的字串部分 |
$' | 插入字串中匹配項之後的字串部分 |
$n | 如果 n 是一個 1-2 位的數字,則插入第 n 個分組的內容,詳見 捕獲組 |
$<name> | 插入帶有給定 name 的括號內的內容,詳見 捕獲組 |
$$ | 插入字元 $ |
例如:
$&
代表匹配結果。
console.log(
"2022/10/24".replace(/(\d{4}\/\d{2}\/\d{2})/g, "今天是$&") // 今天是2022/10/24
);
$
`代表匹配結果左邊的文字。
console.log(
"今天是2022/10/24".replace(/(\d{4}\/\d{2}\/\d{2})/g, "$`") // 今天是今天是
);
$n
代表按序號 n 獲取對應捕獲組的文字。
// 替換字串
console.log(
"2022/10/24".replace(/(\d{4})\/(\d{2})\/(\d{2})/g, "$2/$3/$1") // 10/24/2022
);
// 命名捕獲組
console.log(
"2022/10/24".replace(
/(?<year>\d{4})\/(?<month>\d{2})\/(?<day>\d{2})/g,
"$<month>/$<day>/$<year>"
)
); // 10/24/2022
$<name>
代表按 name 為捕獲組命名:
// 命名捕獲組
console.log(
"2022/10/24".replace(
/(?<year>\d{4})\/(?<month>\d{2})\/(?<day>\d{2})/g,
"$<month>/$<day>/$<year>"
)
); // 10/24/2022
對於需要“智慧”替換的場景,第二個引數可以是一個函式。
每次匹配都會呼叫這個函式,並且返回的值將作為替換字串插入。
該函式 func(match, p1, p2, ..., pn, offset, input, groups)
帶引數呼叫:
- match —— 匹配項,
- p1, p2, ..., pn —— 捕獲組的內容(如有),
- offset —— 匹配項的位置,
- input —— 源字串,
- groups —— 具有命名的捕獲組的物件。
如果正規表示式中沒有括號,則只有 3 個引數:func(str, offset, input)。
let result = "2022/10/24".replace(
/(\d{4})\/(\d{2})\/(\d{2})/g,
(_, y, m, d) => {
return [m, d, y].join("/");
}
);
console.log(result); // 10/24/2022
如果有許多組,用 rest 引數(…)可以很方便的訪問:
let result = "2022/10/24".replace(/(\d{4})\/(\d{2})\/(\d{2})/g, (...match) => {
return `${match[2]}/${match[3]}/${match[1]}`;
});
console.log(result); // 10/24/2022
或者,如果我們使用的是命名組,則帶有它們的 groups 物件始終是最後一個物件,所以我們可以像這樣獲取它:
let result = "2022/10/24".replace(
/(?<year>\d{4})\/(?<month>\d{2})\/(?<day>\d{2})/g,
(...match) => {
let groups = match.pop();
return `${groups.month}/${groups.day}/${groups.year}`;
}
);
console.log(result); // 10/24/2022
str.replaceAll(str|regexp, str|func)
這個方法與 str.replace 本質上是一樣的,但有兩個主要的區別:
如果第一個引數是一個字串,它會替換 所有出現的 和第一個引數相同的字串 , 而 replace 只會替換 第一個。
如果第一個引數是一個沒有修飾符 g 的正規表示式,則會報錯。帶有修飾符 g,它的工作方式與 replace 相同。
replaceAll 的主要用途是替換所有出現的字串。
像這樣:
// 使用冒號替換所有破折號
console.log("2022/10/24".replaceAll("/", "-")); // 2022-10-24
RegExp.$1-$9
非標準: 該特性是非標準的,請儘量不要在生產環境中使用它!
非標準$1, $2, $3, $4, $5, $6, $7, $8, $9 屬性是包含括號子串匹配的正規表示式的靜態和只讀屬性。
在指令碼中使用如 replace
、test
、match
等方法,如果正則中含有捕獲組,訪問 RegExp 物件的非標準屬性 $1
和 $2
能提取捕獲組裡面的內容。
let str = "I love JavaScript";
console.log(/(love)/i.test(str));
// RegExp.$1 love