量词与贪婪模式
量词有哪几种写法?
- 量词: 附加在字符、字符类或集合之后的"重复次数"说明;
{n}: 恰好 n 次;{n,m}: n 到 m 次;{n,}: 至少 n 次, 无上限;- 简写三兄弟:
+={1,}、?={0,1}、*={0,}; - 可选与重复的区别:
?让符号可有可无,*让符号可缺席也可多次出现;
"I'm 12345 years old".match(/\d{5}/); // 12345
"I'm not 12, but 1234 years old".match(/\d{3,5}/); // 1234
"I'm not 12, but 345678 years old".match(/\d{3,}/); // 345678
"You are 1234567 years old".match(/\d{1,}/); // 1234567
"Should I write color or colour?".match(/colou?r/g); // color, colour
"100 10 1".match(/\d0*/g); // 100, 10, 1
"100 10 1".match(/\d0+/g); // 100, 10 (1 因缺 0 不匹配)
- 找完整数字:
\d{1,}即\d+, 一次取出连续数字;
"+7(903)-123-45-67".match(/\d+/g); // 7, 903, 123, 45, 67
贪婪模式下引擎如何找匹配?
- 默认模式: 贪婪, 被量词修饰的字符尽量多重复;
- 算法:
- 从每个位置尝试匹配整个模式;
- 失败则右移一位;
- 量词内部: 先吃到不能再吃, 再匹配模式的剩余部分;
- 回溯: 剩余部分不匹配时, 逐次减少一次重复再试;
- 步数: 消耗字符多 → 剩余部分失败 → 回退 → 反复至成功或穷尽;
const str = 'a "witch" and her "broom" is one';
str.match(/".+"/g); // ["witch" and her "broom"] 而非两个引号串
- 原因:
.+先吞到字符串末尾, 再逐步回退, 直到最后一个引号出现, 于是跨过了中间的引号;
懒惰模式如何改变重复次数?
- 开启: 在量词后再加一个
?, 得*?、+?、??; - 语义: 先不重复, 立即尝试模式的剩余部分, 失败才加一次重复;
- 注意: 作为量词的
?是"零或一", 紧跟在另一个量词之后才变成"切换为懒惰"; - 局部生效: 只有带
?的那个量词变懒, 其它量词保持贪婪;
const str = 'a "witch" and her "broom" is one';
str.match(/".+?"/g); // "witch", "broom"
"123 456".match(/\d+ \d+?/); // 123 4
- 上例解读:
\d+贪婪吃掉123, 空格匹配,\d+?只吃一个4, 模式随即结束;
贪婪与懒惰各有什么陷阱?
- 贪婪的坑: 量词吃掉过多字符, 把本该分开的多段匹配合成一段;
- 懒惰的坑: 只保证"最早结束", 不保证"不越界", 仍可能跨过结构边界;
// 贪婪: 两个链接挤进一个匹配
'...<a href="link1" class="doc">... <a href="link2" class="doc">...'.match(
/<a href=".*" class="doc">/g,
); // 一整段
// 懒惰: 修好了多链接, 却跨到后面的 <p> 标签
'...<a href="link1" class="wrong">... <p style="" class="doc">...'.match(
/<a href=".*?" class="doc">/g,
); // 跨界匹配
- 解法: 用排除集合限定"可取范围", 如
[^"]*表示"直到最近的引号"; - 效果: 贪婪与懒惰都失效时, 精确的排除集合反而最稳;
'...<a href="link1" class="wrong">... <p style="" class="doc">...'.match(
/<a href="[^"]*" class="doc">/g,
); // null
'...<a href="link1" class="doc">... <a href="link2" class="doc">...'.match(
/<a href="[^"]*" class="doc">/g,
); // 两个链接
- 等价替代:
"[^"]+"与".+?"都能取引号串, 二者思路不同, 按场景选择;
如何用原子模式拼出常见匹配?
- 小数:
\d+\.\d+; - 无属性开标签:
/<[a-z]+>/i; - 更精确的标签名:
<[a-z][a-z0-9]*>, 首位必须是字母, 之后可含数字; - 开或闭标签:
<\/?[a-z][a-z0-9]*>, 可选斜杠\/?需转义斜杠; - 代价规律: 模式越精确, 越长越复杂;
<\w+>更短但更宽松;
"0 1 12.345 7890".match(/\d+\.\d+/g); // 12.345
"<h1>Hi!</h1>".match(/<\/?[a-z][a-z0-9]*>/gi); // <h1>, </h1>
- 选型: 能容忍多余匹配、或事后易于清理时, 用宽松模式; 否则写精确模式;
- 锁定独立词: 用
\b约束范围, 如\b\d{5}\b, 见 锚点与边界; - 引擎优化: 现代引擎可能用内部优化走不同路径, 但复杂模式仍按上述回溯逻辑执行;