环视与粘性
环视解决什么问题?
- 场景: 只想匹配"前面或后面紧邻某模式"的那一段;
- 术语: 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, 需要"串首/行首"语义时用^;