正则贪婪匹配与非贪婪匹配

默认量词是贪婪的,会尽可能多吃字符;加 ? 变成非贪婪,尽可能少吃。在提取标签、括号内容时非常关键。

#type / concept #status / evergreen #resource / javascript #resource / ecmascript

[!info] related notes

正则贪婪匹配与非贪婪匹配

一句话定义

量词默认是贪婪的(greedy):尽可能多吃字符。在量词后加 ? 变成非贪婪的(lazy):尽可能少吃。

贪婪(默认)

量词 * + ? {n,m} 默认都是贪婪的。

const str = "a123b456b";
console.log(str.match(/a.*b/)[0]);
// "a123b456b"  ← 吃到最后一个 b

.* 会从 a 一直往后吃,直到再也找不到 b 才回退一步匹配最后一个 b

非贪婪

在量词后加 ?

const str = "a123b456b";
console.log(str.match(/a.*?b/)[0]);
// "a123b"  ← 吃到第一个 b 就停

常见非贪婪写法:

贪婪非贪婪
**?
++?
???
{n,m}{n,m}?

最典型的使用场景

提取 HTML/XML 标签

const str = "<div>one</div><div>two</div>";

// 贪婪:吃太多
str.match(/<div>.*<\/div>/g);
// ["<div>one</div><div>two</div>"]

// 非贪婪:正确
str.match(/<div>.*?<\/div>/g);
// ["<div>one</div>", "<div>two</div>"]

提取括号内容

const str = "(hello) world (foo)";

// 贪婪
str.match(/\(.*\)/g);
// ["(hello) world (foo)"]

// 非贪婪
str.match(/\(.*?\)/g);
// ["(hello)", "(foo)"]

提取引号内容

const str = 'name="Alice" age="30"';

str.match(/".*?"/g);
// ['"Alice"', '"30"']

原理

正则引擎的回溯机制:

  1. 贪婪:先尽量多吃,发现后面匹配不上了,再往回退一个字符重试
  2. 非贪婪:先尽量少吃,发现后面匹配不上了,再多吃一个字符重试

两者都能达到最终匹配,但路径不同,性能也可能不同。

注意事项

非贪婪不一定更快

非贪婪意味着更多的”试探”。在某些场景下,贪婪反而更快——因为引擎一开始就能确定边界。

不能替代断言

非贪婪解决的是”吃多少”的问题,不是”位置条件”的问题。

如果要匹配”后面跟着 px 的数字”,应该用先行断言,而不是非贪婪:

// 非贪婪做不到这件事
// 正确做法:用断言
"12px".match(/\d+(?=px)/)[0]; // "12"

详见:regex-lookahead-lookbehind

速查

// 贪婪:尽可能多吃
/a.*b/    // 最长匹配
/a.+b/    // 至少一个,最长

// 非贪婪:尽可能少吃
/a.*?b/   // 最短匹配
/a.+?b/   // 至少一个,最短
创建于 2026/3/21 更新于 2026/5/27