正则表达式:贪婪与惰性


一、核心区别

贪婪(Greedy)惰性(Lazy)
写法*+?{n,m}量词后加 ?*?+???{n,m}?
策略先吃撑,再吐出来 — 一口吞下尽可能多的字符,匹配不了再一点点往回退先饿着,再多吃 — 先匹配尽可能少的字符,不够再一个个往后加
结果匹配最长的结果匹配最短的结果

比喻

  • 贪婪:巴蛇吞象——一口吞下整个字符串,发现匹配不了,再从后面一点点吐出来,直到能匹配为止
  • 惰性:太懒了——先吃一个字符,如果不匹配再多添一个,够了就停

二、经典示例

对字符串 "aa{b{aa}bb{aaa}b}aa" 匹配花括号内容:

var re1 = /\{.*\}/g;    // 贪婪
var re2 = /\{.*?\}/g;   // 惰性
 
"aa{b{aa}bb{aaa}b}aa".match(re1);
// → ["{b{aa}bb{aaa}b}"]  ← 匹配了最外层的一整个大块
 
"aa{b{aa}bb{aaa}b}aa".match(re2);
// → ["{b{aa}", "{aaa}"]  ← 匹配了两个最短的片段

匹配过程图解

贪婪 \{.*\}

{b{aa}bb{aaa}b}aa
^                    ← 遇到 { 开始匹配
{b{aa}bb{aaa}b}aa
 ^^^^^^^^^^^^^^^^^   ← .* 一口吞到字符串末尾
{b{aa}bb{aaa}b}aa
                ^^   ← 末尾不是 },往回吐
{b{aa}bb{aaa}b}aa
               ^     ← 找到 },匹配成功 → "{b{aa}bb{aaa}b}"

惰性 \{.*?\}

{b{aa}bb{aaa}b}aa
^                    ← 遇到 { 开始匹配
{b{aa}bb{aaa}b}aa
 ^                   ← .*? 先不吃,看下一个是不是 } → 不是(b),多吃一个
{b{aa}bb{aaa}b}aa
  ^                  ← 下一个是 {,不是 } → 多吃一个
...
{b{aa}bb{aaa}b}aa
     ^               ← 下一个是 },匹配成功 → "{b{aa}"
                     ← 继续找下一个匹配 → "{aaa}"

三、所有贪婪量词与惰性对照

贪婪惰性含义(贪婪)含义(惰性)
**?0 次或多次,尽量多0 次或多次,尽量少
++?1 次或多次,尽量多1 次或多次,尽量少
???0 次或 1 次,优先 1 次0 次或 1 次,优先 0 次
{n,m}{n,m}?n 到 m 次,尽量多(趋向 m)n 到 m 次,尽量少(趋向 n)
{n,}{n,}?至少 n 次,尽量多至少 n 次,尽量少

四、实际应用

提取 HTML 标签

var html = '<div>hello</div><span>world</span>';
 
html.match(/<.*>/g);
// 贪婪 → ["<div>hello</div><span>world</span>"]  ← 整段吞掉
 
html.match(/<.*?>/g);
// 惰性 → ["<div>", "</div>", "<span>", "</span>"]  ← 逐个标签

提取引号内内容

var text = '他说"你好",她说"再见"';
 
text.match(/".*"/g);
// 贪婪 → ['"你好",她说"再见"']  ← 从第一个引号吃到最后一个引号
 
text.match(/".*?"/g);
// 惰性 → ['"你好"', '"再见"']  ← 分别匹配

经验法则

大部分情况下,如果你想匹配成对的分隔符之间的内容(引号、括号、标签等),用惰性匹配更安全。或者更精确的做法是用排除字符集,比如 "[^"]*" 匹配引号内容,完全不需要纠结贪婪还是惰性。