前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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 热点
旧版
首页程序员面试题库JavaScript 正则 贪婪与懒惰量词区别
JavaScript数据处理

正则的贪婪匹配和非贪婪匹配有什么区别?

量词默认是贪婪的,先尽可能多地吃字符,后面匹配不上再一个个吐回来。在量词后面加 ? 就变成懒惰,先吃最少的,不够再一个个多吃。所以 ".+" 会从第一个引号一直匹配到最后一个引号,".+?" 在第一个右引号就停下。

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

同一个量词的两种试探顺序

  1. 贪婪先吃到上限,后面失败就一个个吐回去再试
  2. 懒惰先吃到下限,后面失败就一个个多吃再试
  3. 回溯某条路走不通时退回上一个选择点换一种吃法

贪婪和懒惰只改变试探顺序,不改变起点。引擎总是先保证匹配从最左边能成功的位置开始。

核心回答

先记住这个答案

区别在于量词先试多少个字符。贪婪量词先尽可能多地吃,后面的表达式匹配不上时,再一个个吐回去重试;懒惰量词是在量词后面加一个 ?,比如 *?、+?,它先吃最少的,不够再一个个多吃。在 say "one" then "two" 里,/".+"/ 会匹配到 "one" then "two",/".+?"/ 只匹配 "one"。不过实际项目里更可靠的写法往往是 /"[^"]+"/,直接规定引号之间不能再有引号。

  • *、+、?、{m,n} 默认贪婪
  • 量词后加 ? 变懒惰,先少后多
  • 懒惰不等于最短,最左边的起点优先
  • 能用 [^"]+ 限定字符就别靠懒惰

用引号匹配看两者的差别

最常见的需求是把引号里的内容取出来。一段文本里有两对引号时,差别就出来了。

同一段文本,三种写法JavaScript
const text = 'say "one" then "two"'

console.log(text.match(/".+"/)[0])        // 贪婪:一路吃到最后一个引号
console.log(text.match(/".+?"/)[0])       // 懒惰:遇到第一个能收尾的引号就停
console.log(text.match(/"[^"]+"/g).join('|')) // 排除引号,意图最明确
查看输出与解释
"one" then "two"
"one"
"one"|"two"

贪婪版本出人意料地把中间的 then 也吞了进去。很多人写爬虫或者解析模板时遇到的「匹配多了」,基本都是这个原因。 第三种写法用的是排除型字符类 [^"],意思是「除了引号以外的任意字符」。它从根上规定了引号中间不能再出现引号,所以不需要依赖贪婪还是懒惰,意图也写得最清楚。

背后是回溯在工作

要理解为什么贪婪会匹配到最后一个引号,得知道正则引擎是怎么试的。JavaScript 的正则引擎是回溯型的:遇到有多种吃法的地方就记下一个选择点,先按一种吃法往下走,走不通再退回来换一种。

一步步看 <.+> 怎么匹配JavaScript
const html = '<a><b>'

// 贪婪:.+ 先吃到字符串末尾,发现后面还需要 >,再一个个往回吐
console.log(html.match(/<.+>/)[0])
// 懒惰:.+? 先只吃 1 个字符 a,后面正好是 >,立刻成功
console.log(html.match(/<.+?>/)[0])
查看输出与解释
<a><b>
<a>

贪婪的过程是:< 匹配第一个字符,.+ 一口气吃掉 a><b> 直到末尾。接着表达式还需要一个 >,但已经没有字符了,于是 .+ 吐出最后一个 >,这时 > 能匹配上,整体成功,结果就是整串。 懒惰的过程反过来:.+? 至少要吃一个字符,先吃 a,然后检查后面是不是 >,是的,成功,结果是 <a>。 所以两者的结果都满足整条正则,只是试探顺序不同,先找到的答案不同。量词本身的上下限不变,+? 也至少要吃一个字符。

懒惰不等于最短匹配
'aab'.match(/a+?b/) 得到的是 aab,而不是更短的 ab。原因是引擎先从下标 0 开始尝试,a+? 先吃一个 a,后面是 a 不是 b,于是多吃一个,后面是 b,成功。从下标 0 就已经能成功了,引擎不会再去下标 1 找更短的。匹配的起点总是最左边能成功的位置,懒惰只影响从这个起点往后吃多少。
回答前,多想一步

容易答错的地方

以为懒惰量词只会匹配一个字符
懒惰只是从最少开始试。后面的表达式需要它多吃时,它会一直往后扩张。/".+?"/ 遇到 "hello world" 一样能吃下中间的 11 个字符,它停下的条件是后面的 " 能匹配上,而不是吃够一个就停。
以为懒惰一定比贪婪快
两者只是试探顺序不同,哪个快取决于文本。目标内容很短、离起点近时懒惰试得少;目标内容一直延伸到文本末尾时,贪婪一口吃到底再少量回吐,反而比懒惰一个个试要快。真正影响性能的是有没有大量回溯,而不是加不加 ?。
试着用自己的话回答

面试官还会怎么问?

什么是灾难性回溯?

正则里有嵌套的量词,而且内外层能匹配同样的字符时,比如 /(a+)+b/,面对一串 a 后面没有 b 的输入,引擎要把「这串 a 怎么分给内外两层」的所有分法都试一遍,尝试次数随长度指数增长,几十个字符就能让页面卡死。这类问题叫 ReDoS。解决办法是消除嵌套量词的重叠,改成 /a+b/,而不是把 + 改成 +?,懒惰一样要试完所有分法。

.*? 能用来解析 HTML 吗?

简单提取可以,比如从一段固定格式的片段里取某个标签的文本。但 HTML 有嵌套、属性里可能有 >、有注释和转义,/<div>.*?<\/div>/ 遇到嵌套的 <div> 会在第一个 </div> 就停下,拿到半截内容。浏览器里用 DOMParser,Node.js 里用专门的 HTML 解析库。

{2,4}? 这种写法是什么意思?

区间量词也可以变懒惰。/a{2,4}/ 对 aaaa 会匹配 4 个,/a{2,4}?/ 先只匹配 2 个,后面没有其他要求就直接成功。? 本身也是量词,?? 表示「0 个或 1 个,优先 0 个」,比较少见,但面试偶尔会问。

从一道题,走向一组知识

把知识连起来

数据处理

正则的命名捕获组怎么用,和 match[1] 这种写法比好在哪?

贪婪量词吞多了字符时,后面的捕获组会拿到意料之外的值,用命名组打印出来最容易看出是哪一段被吞了。

数据处理

正则里的前瞻和后顾断言怎么用,有什么区别?

当边界不好用排除字符类表达时,前瞻断言是贪婪和懒惰之外另一种限定匹配终点的办法。

数据处理

正则的 y 标志和 g 标志有什么区别,y 适合用在哪?

用 y 标志写词法分析器时,每条 token 规则的量词是贪婪还是懒惰,直接决定一个 token 切多长。

放回知识体系中复习JavaScript 进阶知识

参考资料

  • Quantifiers

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

本题目录
  1. 先记住这个答案
  2. 用引号匹配看两者的差别
  3. 背后是回溯在工作
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

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

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