Javascript 正則表達(dá)式和字符串的方法

2023-02-17 11:02 更新

在本文中,我們將深入介紹與正則表達(dá)式配合使用的各種方法。

str.match(regexp)

str.match(regexp) 方法在字符串 str 中查找 regexp 的匹配項(xiàng)。

它有 3 種模式:

  1. 如果 regexp 不帶有修飾符 g,則它以數(shù)組的形式返回第一個(gè)匹配項(xiàng),其中包含捕獲組和屬性 index(匹配項(xiàng)的位置)、input(輸入字符串,等于 str):

    let str = "I love JavaScript";
    
    let result = str.match(/Java(Script)/);
    
    alert( result[0] );     // JavaScript(完全匹配)
    alert( result[1] );     // Script(第一個(gè)分組)
    alert( result.length ); // 2
    
    // 其他信息:
    alert( result.index );  // 7(匹配位置)
    alert( result.input );  // I love JavaScript(源字符串)
  2. 如果 regexp 帶有修飾符 g,則它將返回一個(gè)包含所有匹配項(xiàng)的數(shù)組,但不包含捕獲組和其它詳細(xì)信息。

    let str = "I love JavaScript";
    
    let result = str.match(/Java(Script)/g);
    
    alert( result[0] ); // JavaScript
    alert( result.length ); // 1
  3. 如果沒有匹配項(xiàng),則無論是否帶有修飾符 g,都將返回 null

    這是一個(gè)重要的細(xì)微差別。如果沒有匹配項(xiàng),我們得到的不是一個(gè)空數(shù)組,而是 null。忘記這一點(diǎn)很容易出錯(cuò),例如:

    let str = "I love JavaScript";
    
    let result = str.match(/HTML/);
    
    alert(result); // null
    alert(result.length); // Error: Cannot read property 'length' of null

    如果我們希望結(jié)果是一個(gè)數(shù)組,我們可以這樣寫:

    let result = str.match(regexp) || [];

str.matchAll(regexp)

最近新增的特性

這是一個(gè)最近添加到 JavaScript 的特性。 舊式瀏覽器可能需要 polyfills.

方法 str.matchAll(regexp) 是 str.match 的“更新、改進(jìn)”的變體。

它主要用來搜索所有組的所有匹配項(xiàng)。

與 match 相比有 3 個(gè)區(qū)別:

  1. 它返回一個(gè)包含匹配項(xiàng)的可迭代對象,而不是數(shù)組。我們可以用 ?Array.from? 將其轉(zhuǎn)換為一個(gè)常規(guī)數(shù)組。
  2. 每個(gè)匹配項(xiàng)均以一個(gè)包含捕獲組的數(shù)組形式返回(返回格式與不帶修飾符 ?g? 的 ?str.match? 相同)。
  3. 如果沒有結(jié)果,則返回的是一個(gè)空的可迭代對象而不是 ?null?。

用法示例:

let str = '<h1>Hello, world!</h1>';
let regexp = /<(.*?)>/g;

let matchAll = str.matchAll(regexp);

alert(matchAll); // [object RegExp String Iterator],不是數(shù)組,而是一個(gè)可迭代對象

matchAll = Array.from(matchAll); // 現(xiàn)在是數(shù)組了

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 的匹配項(xiàng),那么我們就不需要 Array.from 了。

str.split(regexp|substr, limit)

使用正則表達(dá)式(或子字符串)作為分隔符來分割字符串。

我們可以用 ?split? 來分割字符串,像這樣:

alert('12-34-56'.split('-')) // 數(shù)組 ['12', '34', '56']

但同樣,我們也可以用正則表達(dá)式:

alert('12, 34, 56'.split(/,\s*/)) // 數(shù)組 ['12', '34', '56']

str.search(regexp)

方法 str.search(regexp) 返回第一個(gè)匹配項(xiàng)的位置,如果沒找到,則返回 -1

let str = "A drop of ink may make a million think";

alert( str.search( /ink/i ) ); // 10(第一個(gè)匹配位置)

重要限制:search 僅查找第一個(gè)匹配項(xiàng)。

如果我們需要其他匹配項(xiàng)的位置,則應(yīng)使用其他方法,例如用 str.matchAll(regexp) 查找所有位置。

str.replace(str|regexp, str|func)

這是用于搜索和替換的通用方法,是最有用的方法之一。它是搜索和替換字符串的瑞士軍刀。

我們可以在不使用正則表達(dá)式的情況下使用它來搜索和替換子字符串:

// 用冒號替換連字符
alert('12-34-56'.replace("-", ":")) // 12:34-56

不過有一個(gè)陷阱。

當(dāng) replace 的第一個(gè)參數(shù)是字符串時(shí),它只替換第一個(gè)匹配項(xiàng)。

你可以在上面的示例中看到:只有第一個(gè) "-" 被替換為了 ":"。

如要找到所有的連字符,我們不應(yīng)該用字符串 "-",而應(yīng)使用帶 g 修飾符的正則表達(dá)式 /-/g

// 將所有連字符都替換為冒號
alert( '12-34-56'.replace( /-/g, ":" ) )  // 12:34:56

第二個(gè)參數(shù)是替換字符串。我們可以在其中使用特殊字符:

符號 替換字符串中的行為
$& 插入整個(gè)匹配項(xiàng)
$` 插入字符串中匹配項(xiàng)之前的字符串部分
$' 插入字符串中匹配項(xiàng)之后的字符串部分
$n 如果 n 是一個(gè) 1-2 位的數(shù)字,則插入第 n 個(gè)分組的內(nèi)容,詳見 捕獲組
$<name> 插入帶有給定 name 的括號內(nèi)的內(nèi)容,詳見 捕獲組
$$ 插入字符 $

例如:

let str = "John Smith";

// 交換名字和姓氏
alert(str.replace(/(john) (smith)/i, '$2, $1')) // Smith, John

對于需要“智能”替換的場景,第二個(gè)參數(shù)可以是一個(gè)函數(shù)。

每次匹配都會調(diào)用這個(gè)函數(shù),并且返回的值將作為替換字符串插入。

該函數(shù) func(match, p1, p2, ..., pn, offset, input, groups) 帶參數(shù)調(diào)用:

  1. ?match? —— 匹配項(xiàng),
  2. ?p1, p2, ..., pn? —— 捕獲組的內(nèi)容(如有),
  3. ?offset? —— 匹配項(xiàng)的位置,
  4. ?input? —— 源字符串,
  5. ?groups? —— 具有命名的捕獲組的對象。

如果正則表達(dá)式中沒有括號,則只有 3 個(gè)參數(shù):func(str, offset, input)。

例如,將所有匹配項(xiàng)都大寫:

let str = "html and css";

let result = str.replace(/html|css/gi, str => str.toUpperCase());

alert(result); // HTML and CSS

將每個(gè)匹配項(xiàng)替換為其在字符串中的位置:

alert("Ho-Ho-ho".replace(/ho/gi, (match, offset) => offset)); // 0-3-6

在下面的示例中,有兩對括號,因此將使用 5 個(gè)參數(shù)調(diào)用替換函數(shù):第一個(gè)是完全匹配項(xiàng),然后是 2 對括號,然后是(在示例中未使用)匹配位置和源字符串:

let str = "John Smith";

let result = str.replace(/(\w+) (\w+)/, (match, name, surname) => `${surname}, ${name}`);

alert(result); // Smith, John

如果有許多組,用 rest 參數(shù)(…)可以很方便的訪問:

let str = "John Smith";

let result = str.replace(/(\w+) (\w+)/, (...match) => `${match[2]}, ${match[1]}`);

alert(result); // Smith, John

或者,如果我們使用的是命名組,則帶有它們的 groups 對象始終是最后一個(gè)對象,所以我們可以像這樣獲取它:

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

使用函數(shù)可以為我們提供終極替換功能,因?yàn)樗梢垣@取匹配項(xiàng)的所有信息,可以訪問外部變量,可以做任何事。

str.replaceAll(str|regexp, str|func)

這個(gè)方法與 str.replace 本質(zhì)上是一樣的,但有兩個(gè)主要的區(qū)別:

  1. 如果第一個(gè)參數(shù)是一個(gè)字符串,它會替換 所有出現(xiàn)的 和第一個(gè)參數(shù)相同的字符串?,?而 replace 只會替換 第一個(gè)。
  2. 如果第一個(gè)參數(shù)是一個(gè)沒有修飾符 g 的正則表達(dá)式,則會報(bào)錯(cuò)。帶有修飾符 g,它的工作方式與 replace 相同。

replaceAll 的主要用途是替換所有出現(xiàn)的字符串。

像這樣:

// 使用冒號替換所有破折號
alert('12-34-56'.replaceAll("-", ":")) // 12:34:56

regexp.exec(str)

regexp.exec(str) 方法返回字符串 str 中的 regexp 匹配項(xiàng)。與以前的方法不同,它是在正則表達(dá)式而不是在字符串上調(diào)用的。

它的行為取決于正則表達(dá)式是否具有修飾符 g。

如果沒有修飾符 g,則 regexp.exec(str) 會返回與 第一個(gè)匹配項(xiàng),就像 str.match(regexp) 那樣。這種行為并沒有帶來任何新的東西。

但是,如果有修飾符 g,那么:

  • 調(diào)用 regexp.exec(str) 會返回第一個(gè)匹配項(xiàng),并將緊隨其后的位置保存在屬性 regexp.lastIndex 中。
  • 下一次這樣的調(diào)用會從位置 regexp.lastIndex 開始搜索,返回下一個(gè)匹配項(xiàng),并將其后的位置保存在 regexp.lastIndex 中。
  • ……以此類推。
  • 如果沒有匹配項(xiàng),則 regexp.exec 返回 null,并將 regexp.lastIndex 重置為 0。

因此,重復(fù)調(diào)用會一個(gè)接一個(gè)地返回所有匹配項(xiàng),使用屬性 regexp.lastIndex 來跟蹤當(dāng)前搜索位置。

過去,在將 str.matchAll 方法添加到 JavaScript 之前,會在循環(huán)中調(diào)用 regexp.exec 來獲取組的所有匹配項(xiàng):

let str = 'More about JavaScript at https://javascript.info';
let regexp = /javascript/ig;

let result;

while (result = regexp.exec(str)) {
  alert( `Found ${result[0]} at position ${result.index}` );
  // 在位置 11 找到了 JavaScript,然后
  // 在位置 33 找到了 javascript
}

這現(xiàn)在也有效,盡管對于較新的瀏覽器 str.matchAll 通常更方便。

我們可以通過手動(dòng)設(shè)置 lastIndex,用 regexp.exec 從給定位置進(jìn)行搜索。

例如:

let str = 'Hello, world!';

let regexp = /\w+/g; // 沒有修飾符 "g",lastIndex 屬性會被忽略
regexp.lastIndex = 5; // 從第 5 個(gè)位置搜索(從逗號開始)

alert( regexp.exec(str) ); // world

如果正則表達(dá)式帶有修飾符 y,則搜索將精確地在 regexp.lastIndex 位置執(zhí)行,不會再進(jìn)一步。

讓我們將上面示例中的 g 修飾符替換為 y。現(xiàn)在沒有找到匹配項(xiàng),因?yàn)樵谖恢?nbsp;5 處沒有單詞:

let str = 'Hello, world!';

let regexp = /\w+/y;
regexp.lastIndex = 5; // 在位置 5 精確查找

alert( regexp.exec(str) ); // null

當(dāng)我們需要通過正則表達(dá)式在確切位置而不是其后的某處從字符串中“讀取”某些內(nèi)容時(shí),這很方便。

regexp.test(str)

方法 regexp.test(str) 查找匹配項(xiàng),然后返回 true/false 表示是否存在。

例如:

let str = "I love JavaScript";

// 這兩個(gè)測試相同
alert( /love/i.test(str) ); // true
alert( str.search(/love/i) != -1 ); // true

一個(gè)否定答案的例子:

let str = "Bla-bla-bla";

alert( /love/i.test(str) ); // false
alert( str.search(/love/i) != -1 ); // false

如果正則表達(dá)式帶有修飾符 g,則 regexp.test 從 regexp.lastIndex 屬性開始查找并更新此屬性,就像 regexp.exec 一樣。

因此,我們可以用它從給定位置進(jìn)行查找:

let regexp = /love/gi;

let str = "I love JavaScript";

// 從位置 10 開始搜索:
regexp.lastIndex = 10;
alert( regexp.test(str) ); // false(沒有匹配項(xiàng))

相同的全局正則表達(dá)式在不同的源字符串上測試可能會失敗

如果我們在不同的源字符串上應(yīng)用相同的全局正則表達(dá)式,可能會出現(xiàn)錯(cuò)誤的結(jié)果,因?yàn)?nbsp;regexp.test 的調(diào)用會增加 regexp.lastIndex 屬性值,因此在另一個(gè)字符串中的搜索可能是從非 0 位置開始的。

例如,這里我們在同一文本上調(diào)用 regexp.test 兩次,而第二次調(diào)用失敗了:

let regexp = /javascript/g;  // (新建立的 regexp:regexp.lastIndex=0)

alert( regexp.test("javascript") ); // true(現(xiàn)在 regexp.lastIndex=10)
alert( regexp.test("javascript") ); // false

這正是因?yàn)樵诘诙€(gè)測試中 regexp.lastIndex 不為零。

如要解決這個(gè)問題,我們可以在每次搜索之前設(shè)置 regexp.lastIndex = 0。或者,不要在正則表達(dá)式上調(diào)用方法,而是使用字符串方法 str.match/search/...,這些方法不使用 lastIndex。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號