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

浏览器兼容性

另见