RegExp: lastIndex

Baseline 已廣泛支援

此特性已相當成熟,可在許多裝置和瀏覽器版本上使用。自 ⁨2015 年 7 月⁩以來,各瀏覽器均已提供此特性。

lastIndex 資料屬性是 RegExp 例項的一個屬性,它指定了下一次匹配的起始索引。

試一試

const regex = /foo/g;
const str = "table football, foosball";

regex.test(str);

console.log(regex.lastIndex);
// Expected output: 9

regex.test(str);

console.log(regex.lastIndex);
// Expected output: 19

一個非負整數。

RegExp: lastIndex 的屬性特性
可寫
可列舉
可配置

描述

僅當正則表示式例項使用了 g 標誌(表示全域性搜尋)或 y 標誌(表示粘性搜尋)時,此屬性才會被設定。當在給定輸入上呼叫 exec() 時,適用以下規則:

  • 如果 lastIndex 大於輸入的長度,exec() 將不會找到匹配項,並且 lastIndex 會被設定為 0。
  • 如果 lastIndex 等於或小於輸入的長度,exec() 將會嘗試從 lastIndex 開始匹配輸入。
    • 如果 exec() 找到匹配項,則 lastIndex 將會被設定為匹配字串在輸入中的結束位置。
    • 如果 exec() 沒有找到匹配項,則 lastIndex 將會被設定為 0。

其他與正則表示式相關的函式,例如 RegExp.prototype.test()String.prototype.match()String.prototype.replace() 等,在底層都會呼叫 exec(),因此它們對 lastIndex 有不同的影響。有關詳細資訊,請參閱它們各自的頁面。

示例

使用 lastIndex

考慮以下語句序列:

js
const re = /(hi)?/g;

匹配空字串。

js
console.log(re.exec("hi"));
console.log(re.lastIndex);

返回 ["hi", "hi"],此時 lastIndex 等於 2。

js
console.log(re.exec("hi"));
console.log(re.lastIndex);

返回 ["", undefined],一個空陣列,其第零個元素是匹配的字串。在這種情況下,空字串是因為 lastIndex 是 2(並且仍然是 2),而 hi 的長度是 2。

將 lastIndex 與粘性正則表示式一起使用

lastIndex 屬性是可寫的。你可以設定它來讓正則表示式從指定的索引開始下一次搜尋。

y 標誌幾乎總是需要設定 lastIndex。它總是嚴格地在 lastIndex 處匹配,並且不會嘗試後面的位置。這對於編寫解析器非常有用,當你只想在當前位置匹配標記時。

js
const stringPattern = /"[^"]*"/y;
const input = `const message = "Hello world";`;

stringPattern.lastIndex = 6;
console.log(stringPattern.exec(input)); // null

stringPattern.lastIndex = 16;
console.log(stringPattern.exec(input)); // ['"Hello world"']

重置 lastIndex

g 標誌也受益於設定 lastIndex。一個常見的用例是當字串在全域性搜尋過程中被修改時。在這種情況下,如果字串變短,我們可能會錯過特定的匹配。我們可以透過重置 lastIndex 來避免這種情況。

js
const mdLinkPattern = /\[[^[\]]+\]\((?<link>[^()\s]+)\)/dg;

function resolveMDLink(line) {
  let match;
  let modifiedLine = line;
  while ((match = mdLinkPattern.exec(modifiedLine))) {
    const originalLink = match.groups.link;
    const resolvedLink = originalLink.replaceAll(/^files|\/index\.md$/g, "");
    modifiedLine =
      modifiedLine.slice(0, match.indices.groups.link[0]) +
      resolvedLink +
      modifiedLine.slice(match.indices.groups.link[1]);
    // Rewind the pattern to the end of the resolved link
    mdLinkPattern.lastIndex += resolvedLink.length - originalLink.length;
  }
  return modifiedLine;
}

console.log(
  resolveMDLink(
    "[`lastIndex`](files/en-us/web/javascript/reference/global_objects/regexp/lastindex/index.md)",
  ),
); // [`lastIndex`](/en-us/web/javascript/reference/global_objects/regexp/lastindex)
console.log(
  resolveMDLink(
    "[`ServiceWorker`](files/en-us/web/api/serviceworker/index.md) and [`SharedWorker`](files/en-us/web/api/sharedworker/index.md)",
  ),
); // [`ServiceWorker`](/en-us/web/api/serviceworker) and [`SharedWorker`](/en-us/web/api/sharedworker)

嘗試刪除 mdLinkPattern.lastIndex += resolvedLink.length - originalLink.length 這一行並執行第二個示例。你會發現第二個連結沒有被正確替換,因為在字串變短後,lastIndex 已經超過了連結的索引。

警告: 此示例僅用於演示。要處理 Markdown,你可能應該使用解析庫而不是正則表示式。

最佳化搜尋

你可以透過將 lastIndex 設定到一個可以忽略先前可能出現的匹配的位置來最佳化搜尋。例如,而不是這樣做:

js
const stringPattern = /"[^"]*"/g;
const input = `const message = "Hello " + "world";`;

// Pretend we've already dealt with the previous parts of the string
let offset = 26;
const remainingInput = input.slice(offset);
const nextString = stringPattern.exec(remainingInput);
console.log(nextString[0]); // "world"
offset += nextString.index + nextString.length;

考慮這個:

js
stringPattern.lastIndex = offset;
const nextString = stringPattern.exec(remainingInput);
console.log(nextString[0]); // "world"
offset = stringPattern.lastIndex;

這可能更具效能優勢,因為我們避免了字串切片。

避免副作用

exec() 引起的副作用可能令人困惑,特別是當每次 exec() 的輸入都不同時。

js
const re = /foo/g;
console.log(re.test("foo bar")); // true
console.log(re.test("foo baz")); // false, because lastIndex is non-zero

當你手動修改 lastIndex 時,這會更加令人困惑。為了控制副作用,請記住在處理完每個輸入後重置 lastIndex

js
const re = /foo/g;
console.log(re.test("foo bar")); // true
re.lastIndex = 0;
console.log(re.test("foo baz")); // true

透過一些抽象,你可以要求在每次 exec() 呼叫之前將 lastIndex 設定為特定值。

js
function createMatcher(pattern) {
  // Create a copy, so that the original regex is never updated
  const regex = new RegExp(pattern, "g");
  return (input, offset) => {
    regex.lastIndex = offset;
    return regex.exec(input);
  };
}

const matchFoo = createMatcher(/foo/);
console.log(matchFoo("foo bar", 0)[0]); // "foo"
console.log(matchFoo("foo baz", 0)[0]); // "foo"

規範

規範
ECMAScript® 2026 語言規範
# sec-properties-of-regexp-instances

瀏覽器相容性

另見