ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

正则表达式的贪婪与懒惰量词:回溯机制、Lazy 模式与 JavaScript 实战

正则表达式的贪婪与懒惰量词:回溯机制、Lazy 模式与 JavaScript 实战 文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载正则表达式中的量词、*、?、{n}默认以贪婪模式工作匹配引擎会尽可能多地吞入字符再在失败时逐步回溯而量词后追加的?可切换为懒惰模式让引擎尽量少重复。本文以本仓库《Modern JavaScript Tutorial》中 贪婪与懒惰量词章节 为骨架结合 懒惰量词专项练习题 的官方题解系统讲解贪婪/懒惰两种模式的底层匹配流程、回溯backtracking机制以及用字符排除法如[^]精细调优贪婪搜索的替代思路并给出可直接运行的 JavaScript 验证代码。读完你将能准确预测任意带量词正则的匹配结果并写出不会越过边界的正则表达式。从一个容易答错的题开始/\d? \d?/章节配套练习 task.md 提出了一个问题alert( 123 456.match(/\d? \d?/g) ); // ?直觉上\d?是懒惰量词似乎每个只取一个数字那结果会不会是1 4官方题解 solution.md 给出的正确答案是The result is:123 4. 首先懒惰的\d?试图尽量少地取数字但它必须到达空格所以它取到了123。然后第二个\d?只取一个数字因为这已经足够了。123 456匹配/\d? \d?/g的结果是123 4。原因分两步第一个\d?取到123懒惰模式尽量少重复但当它在1之后尝试匹配模式其余部分空格 时发现不匹配于是必须再吃进2、3直到到达空格才满足——此时它已经吞下了123第二个\d?只取4懒惰模式在拿到一个数字4后发现模式已经结束后面没有更多需要匹配的内容于是立即停止不必继续。这个例子揭示了懒惰量词的本质它并不是固定匹配最少字符而是在匹配模式其余部分与多重复一次之间优先选择前者。只有当前缀字符不足以让模式的后续部分匹配成功时懒惰量词才被迫多取一个字符。先回顾量词基础、*、?与{n}要理解贪婪/懒惰先要明确量词本身。在 量词章节 中定义了这些基础语法量词含义等价写法{n}恰好 n 次\d{5}等价于\d\d\d\d\d{n,m}n 到 m 次\d{3,5}匹配 3~5 位数字{n,}n 次及以上\d{3,}匹配 3 位以上数字序列一次或多次{1,}?零次或一次可选{0,1}*零次或多次{0,}例如从7(903)-123-45-67中提取所有号码/\d/g就能得到7,903,123,45,67。量词追加在字符、字符类或[...]集合之后用于指定重复次数。而本文要讨论的懒惰模式则是给这些量词本身再追加一个?变成*?、?、??。注意区分单独的?是一个量词零或一但当它跟在另一个量词后面甚至跟在自己后面如??时含义改变为——把该量词的匹配模式从贪婪切换为懒惰。贪婪搜索从吞到尽头到逐步回溯章节用经典的witch and her broom例子演示了贪婪模式的问题。设文本为let str a witch and her broom is one;直觉上我们想找到两个被引号包裹的字符串witch和broom但使用/./g点号匹配除换行外的任意字符let regexp /./g; let str a witch and her broom is one; alert( str.match(regexp) ); // witch and her broom结果只有一个匹配witch and her broom把中间所有内容都吞掉了。这正是贪婪模式的原罪——greediness is the cause of all evil。正则引擎的通用匹配算法要理解原因先看引擎的顶层算法见 article.md从字符串的每一个位置开始尝试在该位置匹配整个模式若失败则前进到下一个位置继续尝试。贪婪模式的逐步回溯过程对模式.在a witch and her broom is one上的匹配官方图解witch_greedy1.svg 起完整还原了引擎动作第一个模式字符是引号。引擎从 0 号位置开始遇到a失败逐位前移最终在第 3 个位置找到引号引号命中后引擎开始匹配其余部分.。.匹配除换行外的任意字符于是w命中由于有量词点号不断重复、逐字符吞入。因为所有字符都满足.引擎一直吃到字符串末尾才停下此时.已结束引擎尝试匹配模式的下一字符却发现字符串已到尽头。引擎意识到.吃得太多了于是开始回溯backtrack——把量词的匹配缩短一个字符缩短后假设.在字符串倒数第二个字符处结束但末尾字符是e仍匹配不上。继续回溯每步减少一次重复逐个检查引擎不断缩短.的重复次数直到模式其余部分在某处命中为止匹配完成。首个匹配是witch and her broom若带g标志搜索从该匹配末尾继续剩余字符串is one中再无引号于是没有更多结果。**贪婪模式默认的规则是被量词修饰的字符重复尽可能多的次数。**引擎先为.吞入最多字符若模式其余部分匹配失败再逐个字符缩短重试。这也是\d会一次吞掉所有连续数字的原因见 量词章节 中str.match(/\d/g)的示例。懒惰模式*?、?、??的原理懒惰模式与贪婪模式相反其含义是重复尽可能少的次数。启用方式是在量词后追加问号*?、?甚至???量词的懒惰形式。对同一文本使用懒惰模式let regexp /.?/g; let str a witch and her broom is one; alert( str.match(regexp) ); // witch, broom这次得到了预期的两个匹配witch和broom。逐帧对比贪婪模式关键差异出现在第 3 步witch_lazy3.svg第一步相同在第 3 个位置找到起始引号第二步相同点号匹配一个w分岔点因为量词处于懒惰模式引擎不再尝试让点号多匹配一次而是立刻停下来尝试匹配模式的其余部分此刻后面的字符是i不是引号失败于是引擎把点号的重复次数加一再试一次witch_lazy4.svg仍然失败于是再加一、再加一……直到点号后的下一个字符恰为引号模式的其余部分命中下一次搜索从当前匹配末尾继续产生第二个结果witch_lazy6.svg。*?与??的运作方式与此完全一致引擎只有在模式的其余部分在当前状态下无法匹配时才增加一次重复。用一句话概括?是先试最少不够再加是先吃最多不行再减。懒惰量词的边界并非全局开关需要注意**懒惰只对带?的那个量词生效其他量词依旧保持贪婪。**回到开头的练习/\d \d?/不带galert( 123 456.match(/\d \d?/) ); // 123 4逐步推导与 solution.md 的结论一致第一个\d处于贪婪模式尽可能多地吃数字得到123后遇空格停下模式中的空格 命中第二个\d?处于懒惰模式只取一个数字4随即检查模式其余部分——但\d?之后已经没有内容模式到此结束匹配完成123 4。懒惰模式没有需求就不重复。这也解释了为什么带g标志的/\d? \d?/g在123 456上仍只产生这一个匹配第一个\d?被迫吃到空格前123第二个\d?取一个数字即止。关于引擎优化的小提示article.md 中有一则说明现代正则引擎可能对内部算法做优化如提前短路或失败快速跳过实际执行与上述教科书级流程略有出入。但对于理解原理和手工推导结果并不需要关心这些内部优化而复杂正则往往难以优化实际搜索过程就与上述描述高度吻合。替代思路用字符排除法精细调优贪婪搜索章节强调懒惰模式并非万能的银弹。在很多场景下排除法negated character class配合贪婪量词是更稳妥、性能更可预测的解法。基础示例[^]替代.?对于引号包裹的字符串可以用pattern:[^]let regexp /[^]/g; let str a witch and her broom is one; alert( str.match(regexp) ); // witch, broom原理模式要求引号后跟一个或多个非引号字符[^]再跟闭合引号。引擎在遇到闭合引号时[^]自然停止不会越过边界。注意这并非懒惰量词的替代品——两种写法逻辑不同各有用武之地。反面教材懒惰模式也会越过边界章节举了一个链接匹配的例子。要匹配形如a href... classdoc的标签用/a href.* classdoc/g单个链接没问题但当文本中出现两个链接时贪婪的.*会把两个链接连同中间内容一并吞入与 witch 例同理改成懒惰的/a href.*? classdoc/g两个链接场景正常但若文本是let str ...a hreflink1 classwrong... p style classdoc...;懒惰的.*?会从第一个a href出发逐字符增长直到遇到第一个 classdoc——而这个后缀其实位于后面的p style classdoc里于是错误地把两段文本匹配进同一结果let str ...a hreflink1 classwrong... p style classdoc...; let regexp /a href.*? classdoc/g; alert( str.match(regexp) ); // a hreflink1 classwrong... p style classdoc这说明懒惰只保证从当前起点出发尽早结束并不保证匹配边界在语义上正确。最终的正确方案是排除法href[^]*——在href属性内取所有字符直到最近的引号语义精确let str1 ...a hreflink1 classwrong... p style classdoc...; let str2 ...a hreflink1 classdoc... a hreflink2 classdoc...; let regexp /a href[^]* classdoc/g; alert( str1.match(regexp) ); // null, 正确本就不该匹配 alert( str2.match(regexp) ); // a hreflink1 classdoc, a hreflink2 classdoc经验法则当你期望量词在某个特定字符前停止排除法通常比懒惰量词更可靠——它从语法上就禁止量词跨越分隔符而不是靠提前尝试结束来碰运气。配套练习中的实战印证本仓库为本章配置了两道配套练习进一步印证贪婪/懒惰与排除法的取舍练习一查找 HTML 注释任务 task.md 要求找出文本中所有 HTML 注释含跨行与空注释!----。官方题解 solution.md 给出的答案是!--.*?--let regexp /!--.*?--/gs; let str ... !-- My -- comment test -- .. !---- .. ; alert( str.match(regexp) ); // !-- My -- comment \n test --, !----要点有二一是懒惰量词.*?让点号在--前及时停止避免吞并相邻注释二是必须加s标志dotAll否则点号不匹配换行跨行注释将无法命中。这也说明懒惰量词与标志位如s、g常需配合使用才能覆盖真实场景。练习二查找 HTML 标签任务 task.md 要求找出所有带属性的开/闭 HTML 标签。题解 solution.md 直接采用排除法/[^]/glet regexp /[^]/g; let str a href/ input typeradio checked b; alert( str.match(regexp) ); // a href/, input typeradio checked, b这里[^]排除与从结构上保证量词不会跨越标签边界前提是属性值内不出现尖括号练习中已声明此简化假设。与witch例中的[^]思路一脉相承用排除法把停止条件写进量词的字符语义里。总结模式行为启用方式典型风险/适用场景贪婪默认量词先重复尽可能多失败后逐次回溯缩短无默认易越过目标边界如.吞并两个引号串配合排除法可精细调优懒惰量词先重复尽可能少每次重复前先尝试匹配模式其余部分失败才递增量词后加?*?、?、??匹配最短内容如!--.*?--但不保证语义边界正确可能跨到无关结构核心结论与 article.md 的 Summary 一致贪婪模式默认行为。\d会吞掉所有可能数字当无法继续吞入遇到非数字或字符串结束时才开始匹配模式其余部分失败则递减重复次数回溯重试。懒惰模式由量词后的?启用。引擎在每次重复之前都先尝试匹配模式的其余部分。懒惰不是万能的当停止字符明确可枚举时排除法贪婪写法如[^]、[^]、href[^]*往往更精确、语义更清晰。何时用懒惰、何时用排除法取决于目标文本结构与容忍度——这正是正则设计中最常见的取舍。赞分享文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载相关推荐JavaScript正则表达式中的贪婪与懒惰模式详解JavaScript正则表达式中的贪婪与懒惰模式详解 正则表达式是JavaScript中强大的文本处理工具而量词 quantifiers 的使用则是正则表达式文档/教程前端Modern JavaScript Tutorial正则表达式中的贪婪与懒惰量词Greedy and Lazy Quantifiers完全指南Modern JavaScript Tutorial正则表达式中的贪婪与懒惰量词Greedy and Lazy Quantifiers完全指南 导读 在文档/教程前端JavaScript 正则表达式中的贪婪模式与懒惰模式详解JavaScript 正则表达式中的贪婪模式与懒惰模式详解 正则表达式是 JavaScript 中强大的文本处理工具而理解量词quantifiers的贪婪创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表