正则表达式:贪婪与惰性
一、核心区别
| 贪婪(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);
// 惰性 → ['"你好"', '"再见"'] ← 分别匹配经验法则
大部分情况下,如果你想匹配成对的分隔符之间的内容(引号、括号、标签等),用惰性匹配更安全。或者更精确的做法是用排除字符集,比如
"[^"]*"匹配引号内容,完全不需要纠结贪婪还是惰性。