跳到主要内容

环视与粘性

环视解决什么问题?​

  • 场景: 只想匹配"前面或后面紧邻某模式"的那一段;
  • 术语: lookahead (前瞻) + lookbehind (后顾) 合称 lookaround (环视);
  • 例子: 从 1 turkey costs 30€ 里只取价格数字, 而不是任意数字;
  • 手工替代: 先全部匹配, 再用每项的 index 在循环里检查上下文; 环视更省事;

四种环视的语法与含义是什么?​

模式类型含义
X(?=Y)正向先行断言X 之后紧跟 Y 才匹配
X(?!Y)负向先行断言X 之后不是 Y 才匹配
(?<=Y)X正向后顾断言X 之前是 Y 才匹配
(?<!Y)X负向后顾断言X 之前不是 Y 才匹配
"1 turkey costs 30€".match(/\d+(?=€)/); // 30, 数字 1 被忽略
"2 turkeys cost 60€".match(/\d+\b(?!€)/g); // 2, 价格被排除
"1 turkey costs $30".match(/(?<=\$)\d+/); // 30, 单独的 1 被跳过
"2 turkeys cost $60".match(/(?<!\$)\b\d+/g); // 2
  • 记忆: ?= / ?<= 表示"必须是"; ?! / ?<! 表示"必须不是"; 带 < 的是回头看;
  • 执行方式: 引擎先找到 X, 再就地检查条件; 条件不成立就跳过这个候选继续找;

环视括号里的内容会进入匹配结果吗?​

  • 默认不会: 环视只是零宽的条件检查, 既不消费字符也不进结果;
  • 例: \d+(?=€) 的结果只有 30, 不含 €;
  • 需要时: 在环视内部再套一层捕获括号, 就能把它取出来;
"1 turkey costs 30€".match(/\d+(?=(€|kr))/); // ['30', '€']
"1 turkey costs $30".match(/(?<=(\$|£))\d+/); // ['30', '$']

多个环视可以叠加吗?​

  • 可以: 都是零宽断言, 连写时都从同一位置检查;
  • X(?=Y)(?=Z): 要求 X 之后同时满足 Y 与 Z;
  • 前提: 两个条件不能互相矛盾, 否则永不匹配;
"1 turkey costs 30€".match(/\d+(?=\s)(?=.*30)/); // 1

环视有兼容性限制吗?​

  • 后顾断言 (?<=) / (?<!): 非 V8 引擎 (如 Safari、IE) 不支持, 需按目标环境评估;
  • 前瞻断言: 普遍可用;

lastIndex 在 g 与 y 下语义有何不同?​

  • 共同点: 环视与 y 都是零宽断言, 不消费上下文;

  • g: 从 lastIndex 起"往后找", 允许跳过中间内容;

  • y: 只在 lastIndex 这个位置"就地试", 该位置不匹配立刻返回 null;

  • 共同点: 匹配成功后都把 lastIndex 更新到匹配结尾之后; 没有 g/y 时 lastIndex 被忽略;

const str = 'let varName = "value"';

const re1 = /\w+/g;
re1.lastIndex = 3;
re1.exec(str)[0]; // varName — 位置 3 是空格, 但 g 继续往后找到位置 4

const re2 = /\w+/y;
re2.lastIndex = 3;
re2.exec(str); // null — 位置 3 不是词, 就地失败
re2.lastIndex = 4;
re2.exec(str)[0]; // varName

为什么词法分析更需要 y 而不是 g?​

  • 需求: 在固定位置"读一个记号", 只想知道这里是什么;
  • 现有手段都不合适: str.match(/\w+/) 只给第一个词; str.match(/\w+/g) 给全部词; 二者都无法指定位置;
  • g 的语义是"起点": 该位置没有匹配时它会越过去继续找, 对词法分析是错误的;
  • y 的语义是"精确锚定": 位置不对就是 null, 这正是需要的答案;
  • 性能: 长文本中若该位置无匹配, g 会一路扫到末尾; y 只检查一个位置; 反复按位置试探时差值显著;

y 与字符串锚点 ^ 有什么区别?​

  • ^: 锚定文本起点 (加 m 时锚定每行行首), 位置本身不可变, 详见 锚点与边界;
  • y: 锚定 lastIndex, 起点由外部设定, 并随每次成功匹配自动前移;
  • 选型: 需要"游标"式连续读取记号时用 y, 需要"串首/行首"语义时用 ^;