跳到主要内容

量词与贪婪模式

量词有哪几种写法?​

  • 量词: 附加在字符、字符类或集合之后的"重复次数"说明;
  • {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, 见 锚点与边界;
  • 引擎优化: 现代引擎可能用内部优化走不同路径, 但复杂模式仍按上述回溯逻辑执行;