前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库matchAll 与 exec 循环匹配的区别
JavaScript数据处理

有了 matchAll,为什么不再推荐用 while 循环调 exec?

exec 循环要自己写 while、依赖正则的 lastIndex 往前推,漏写 g 就死循环。matchAll 直接返回一个迭代器,每一项都和 exec 的结果一样带捕获组、groups 和 index,原正则的 lastIndex 也不会被改。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#数据处理
先看核心答案读代码示例
理解线索

从手动推游标到拿现成的迭代器

  1. exec 循环靠原正则的 lastIndex 前进,状态留在共享对象上
  2. matchAll复制一份正则来遍历,按需一项项产出匹配结果
  3. match 加 g一次返回全部匹配文本,但拿不到捕获组和位置

迭代器只能消费一次。同一个 matchAll 的结果展开两次,第二次拿到的是空数组。

核心回答

先记住这个答案

matchAll 解决的是「拿到所有匹配,并且每个匹配都要捕获组」这个需求。以前只能写 while ((m = re.exec(str)) !== null),自己维护循环,还要依赖正则对象的 lastIndex 往前推。matchAll 把这件事封装成一个迭代器,每一项和 exec 的返回值结构一样,有 m[1]、m.groups、m.index。它内部会复制一份正则去遍历,所以原正则的 lastIndex 不会被改,不会影响别处的调用。

  • matchAll 返回迭代器,要展开或用 for...of 取
  • 每一项都带捕获组、groups 和 index
  • 传正则时必须带 g,否则抛 TypeError
  • 内部复制正则遍历,原实例 lastIndex 不变

以前的写法:exec 循环

在 matchAll 出现之前,str.match(/.../g) 只能返回所有匹配的文本,捕获组全丢了。想要每一次匹配的分组,只能手写 exec 循环。

用 exec 解析查询字符串JavaScript
const re = /(\w+)=(\d+)/g
const text = 'a=1&b=2'
const pairs = []
let m

// 每次 exec 从 re.lastIndex 开始找,找到后把 lastIndex 往后挪
while ((m = re.exec(text)) !== null) {
  pairs.push(m[1] + ':' + m[2])
}

console.log(pairs.join(','), re.lastIndex) // 最后一次失败,lastIndex 归零
查看输出与解释
a:1,b:2 0

这段代码能用,但有三个地方容易出错。第一,必须先在外面声明 m,把赋值写在 while 条件里,很多代码规范不允许这种写法。第二,正则必须带 g,漏写的话 exec 每次都从 0 开始,永远返回第一个匹配,循环停不下来。第三,循环要跑到 exec 返回 null 才会把 lastIndex 归零,如果中途 break,lastIndex 就停在中间,这个正则下次再用就会从中间开始找。

换成 matchAll:一个 for...of 搞定

matchAll 是 ES2020 加入的。它返回的不是数组,而是一个迭代器,可以直接放进 for...of,也可以用 [...] 或 Array.from 展开成数组。

用命名组解析同一段字符串JavaScript
const text = 'a=1&b=2'
const re = /(?<key>\w+)=(?<val>\d+)/g

for (const m of text.matchAll(re)) {
  // 每一项和 exec 的结果一样:完整匹配、index、groups 都在
  console.log(m.index, m[0], m.groups.key, m.groups.val)
}
查看输出与解释
0 a=1 a 1
4 b=2 b 2

不用声明循环变量,不用判断 null,也不用担心死循环。配合命名组,取值时写 m.groups.key 比 m[1] 好读得多,以后在正则前面再加一个分组,这里也不用跟着改下标。 迭代器还有个好处是按需计算。调用 text.matchAll(re) 时并不会立刻把所有匹配都找出来,for...of 走一步才找一个。处理大文本时只想要前几个匹配,直接 break 就行,不会白白找完整篇。

它不改原正则的 lastIndex

matchAll 内部会用原正则的 source 和 flags 新建一个正则对象,遍历时推进的是这个副本的 lastIndex。复制的时候会带上原来的 lastIndex 作为起点。

从下标 2 开始遍历,原正则不受影响JavaScript
const re = /\d/g
re.lastIndex = 2 // 起点会被复制到副本上

const digits = [...'1a2b3'.matchAll(re)].map((m) => m[0])
console.log(digits.join(''), re.lastIndex) // 从下标 2 开始找到 2 和 3,原实例还是 2

try {
  '1a2'.matchAll(/\d/) // 没有 g
} catch (err) {
  console.log(err.name)
}
查看输出与解释
23 2
TypeError

遍历结束后,原来那个 re 的 lastIndex 还是 2,说明遍历没有碰原实例。这对模块级共享的正则很重要,matchAll 用完不会给下一个调用者留下脏状态。 不带 g 的正则会直接抛 TypeError。这是故意设计的:方法名叫「全部匹配」,传一个只能匹配一次的正则多半是写错了,与其悄悄只返回一项,不如直接报错。如果传的是字符串,比如 str.matchAll('a'),内部会自动用它创建一个带 g 的正则。

回答前,多想一步

容易答错的地方

把返回值当数组,直接调 map 或读 length
matchAll 返回的是迭代器,没有 map、filter、length。要么先 [...str.matchAll(re)] 展开,要么 Array.from(str.matchAll(re), (m) => m[1]) 在展开时顺手映射。
把同一个迭代器用两次
迭代器走到头就结束了。const it = str.matchAll(re) 之后先展开一次,再展开第二次得到的是空数组。需要多次使用时,要么先存成数组,要么每次重新调用 matchAll。
试着用自己的话回答

面试官还会怎么问?

match 加 g 和 matchAll 怎么选?

只要所有匹配的文本,比如找出文章里所有的邮箱,str.match(/.../g) 最简洁,返回一个字符串数组。要每个匹配的分组或者位置,比如解析键值对、做高亮需要知道 index,就用 matchAll。注意 match 在没有匹配时返回 null 而不是空数组,直接调 .length 会报错。

只想要第一个匹配,用 matchAll 合适吗?

没必要。只要第一个匹配和它的分组,用不带 g 的 str.match(re) 或 re.exec(str) 就行,结果结构一样。非要用 matchAll 的话,调一次迭代器的 next() 取 value,也不会多找。

replace 的回调和 matchAll 能互相替代吗?

两者都能拿到每一次匹配的分组。replace 的目的是生成新字符串,回调必须返回替换内容;只为了收集数据而用 replace 的回调是在滥用它,意图不清楚。只收集不替换就用 matchAll,要替换就用 replace 或 replaceAll。

从一道题,走向一组知识

把知识连起来

数据处理

正则里的 \p{Script=Han} 是什么,怎么用它匹配汉字?

用 matchAll 配合 \p{Script=Han} 这类属性转义,可以一次拿到文本里所有汉字片段和它们的位置。

数据处理

分块解码时,TextDecoder 为什么要传 stream: true?

流式解码出来的文本是一块块到达的,对分块文本做正则匹配时要注意匹配可能跨越两个块的边界。

参考资料

  • JavaScript:数据处理参考资料

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 以前的写法:exec 循环
  3. 换成 matchAll:一个 for...of 搞定
  4. 它不改原正则的 lastIndex
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑