- 文档/教程
- 前端
【免费下载链接】en.javascript.info
Modern JavaScript Tutorial
本文基于 Modern JavaScript Tutorial(本仓库 9-regular-expressions 章节)中的经典练习展开:编写一个正则表达式,从文本中匹配 CSS 十六进制颜色
#abc(3 位)与#abcdef(6 位),且要求既不多匹配(拒绝 4 位如#abcd)也不少匹配。读完本文,你将掌握捕获组与量词{1,2}的组合用法、词边界\b的防误匹配技巧,以及str.match配合g/i标志的完整实战模式。
任务背景:CSS 十六进制颜色格式
CSS 中十六进制颜色有两种标准写法:3 位缩写形式#rgb(如#3f3)和 6 位完整形式#rrggbb(如#AA00ef)。其中r、g、b均为十六进制数字,即字符0-9或a-f(大小写均可)。
本练习的完整任务描述位于 task.md,要求如下:
- 匹配
#后跟3 或 6 个十六进制数字的颜色; - 恰好3 或 6 位,4 位字符串(如
#abcd)不得匹配; - 测试文本为
color: #3f3; background-color: #AA00ef; and: #abcd,期望输出#3f3与#AA00ef两个结果。
第一步:匹配单个三位十六进制色
先解决最小问题:匹配#后跟 3 个十六进制数字。
十六进制数字的字符集合是a-f与0-9,用字符集(character set)表示即pattern:[a-f0-9](字符集的详细介绍见 字符类章节 后续的字符集与区间讲解)。跟上"恰好 3 个"的量词pattern:{3},就得到:
let regexp = /#[a-f0-9]{3}/i;这里有两个关键点:
pattern:{3}是精确计数量词,表示前面的元素重复恰好 3 次,等价于pattern:[a-f0-9][a-f0-9][a-f0-9](量词{n}的完整说明见 量词章节);- 标志
pattern:i使匹配忽略大小写,从而同时接受#abc与#ABC。若去掉它,#AA00ef中的大写字母AA和EF就无法匹配。
第二步:用捕获组让量词作用于整体
现在需要扩展为"3 位或 6 位"。直观想法是用量词pattern:{1,2}:一个 3 位的十六进制块,出现 1 次就是 3 位颜色,出现 2 次就是 6 位颜色,且不可能出现 3 次(即 9 位),因为量词上限是 2。
但量词只能作用于它前面的一个元素。要让{1,2}作用于"整个 3 位块",必须用括号把pattern:[a-f0-9]{3}包起来——这正是**捕获组(capturing group)**的核心作用之一:让量词作用于括号整体。
let regexp = /#([a-f0-9]{3}){1,2}/i;捕获组的原理在 捕获组章节 中有系统讲解:括号pattern:(...)有两个效果——一是把匹配内容作为独立项放入结果数组,二是把括号整体作为一个单元供后续量词重复。这里我们利用的是第二个效果:pattern:([a-f0-9]{3}){1,2}表示"3 位十六进制块,重复 1 或 2 次"。
注意量词{1,2}是**贪婪(greedy)**的:在 6 位颜色上它会优先尝试匹配 2 次(即 6 位),只在失败时才回退到 1 次。贪婪/回溯的完整算法见 贪婪与惰性量词章节。
第三步:添加g标志,一次性找出全部颜色
在浏览器或 Node 环境中运行如下完整示例(来自 solution.md):
let regexp = /#([a-f0-9]{3}){1,2}/gi; let str = "color: #3f3; background-color: #AA00ef; and: #abcd"; alert( str.match(regexp) ); // #3f3 #AA00ef #abc标志pattern:g让str.match返回所有匹配组成的数组。此时的输出为#3f3、#AA00ef,以及一个多余的#abc——问题出现了。
第四步:发现边界误匹配
观察测试字符串中的#abcd:它是 4 位十六进制,按任务要求不应匹配。但上面的正则把#abc(前 3 位)当成了一个合法的 3 位颜色,在#abcd内部完成了匹配,结果多输出了一个#abc。
原因在于正则引擎从#abcd的第 0 位#开始尝试:#匹配,[a-f0-9]{3}贪婪地吃掉abc,量词{1,2}尝试再来一组,但下一个字符d之后没有第 7 个字符可凑成第二组,于是回退为只匹配 1 组,匹配成功——完全没有意识到后面还跟着一个d。
第五步:用词边界\b切断误匹配
解决方案是在模式末尾追加词边界断言pattern:\b:
let regexp = /#([a-f0-9]{3}){1,2}\b/gi; let str = "color: #3f3; background-color: #AA00ef; and: #abcd"; alert( str.match(regexp) ); // #3f3 #AA00efpattern:\b是一个零宽度的位置断言,它检查当前位置是否为词边界。根据 词边界章节 的定义,满足以下三种情况之一即为词边界:
- 字符串开头且首字符是单词字符
\w; - 两个相邻字符中一个是单词字符
\w、另一个不是; - 字符串结尾且末字符是单词字符
\w。
这里\w指拉丁字母、数字或下划线。套用到本例:
- 在
#3f3之后是分号;——3是单词字符,;不是,二者交界处是词边界,\b成立,匹配成功; - 在
#AA00ef之后是分号——同理成立,匹配成功; - 在
#abc之后紧跟着d——c与d都是单词字符,交界处不是词边界,\b失败,整个#abcd从#开始的匹配被否决,正则引擎转而尝试下一个位置,最终不再产生多余匹配。
于是输出精确地收敛为#3f3与#AA00ef,与任务期望完全一致。
扩展思考与相关变式
改用非捕获组?:
如果只想借括号让量词作用于整体、却不想让[a-f0-9]{3}的内容作为独立数组项返回,可以把捕获组换成非捕获组pattern:(?:...):
let regexp = /#(?:[a-f0-9]{3}){1,2}\b/gi; // str.match(regexp) 仍返回 ["#3f3", "#AA00ef"] // 但每个匹配不再附带额外的分组内容非捕获组?:的机制同样收录于 捕获组章节。
兄弟练习:匹配 6 位十六进制颜色
本仓库 量词章节 下另有一个变式练习 find-html-colors-6hex,专门匹配恰好 6 位十六进制颜色。其解答思路与本文形成对照:先写出 6 位模式再附加排除条件,可对比学习量词边界控制的两种不同取舍。
兄弟练习:MAC 地址
捕获组章节 的 MAC 地址练习用到了同类手法:pattern:[0-9a-f]{2}(:[0-9a-f]{2}){5}——先用{2}限定单组,再用捕获组 +{5}重复整组,并通过^...$锚定整体。它与本文"用括号把原子块包起来再叠量词"的思路完全同构,可互相印证。
小结
本练习浓缩了四个正则核心知识点的协同运用:
| 知识点 | 本练习中的应用 | 仓库中的完整讲解 |
|---|---|---|
字符集[a-f0-9] | 表达十六进制数字集合 | 字符类章节 |
量词{3}、{1,2} | 精确 3 位、允许 1 或 2 组 | 量词章节 |
捕获组(...) | 让量词作用于 3 位块整体 | 捕获组章节 |
词边界\b | 拒绝 4 位等"超长"十六进制串 | 词边界章节 |
最终可复用的正则表达式为:
/#([a-f0-9]{3}){1,2}\b/gi拆解读法:#字面量 → 捕获组([a-f0-9]{3})(3 位十六进制块)→ 贪婪量词{1,2}(整块重复 1 或 2 次)→ 词边界\b(拒绝后续紧跟十六进制字符的误匹配)→g全局搜索、i忽略大小写。这一"原子块 + 捕获组 + 边界收尾"的组合模式,在颜色、MAC 地址、日期、域名等一切"固定长度分块"文本的解析中都可直接复用。
- 文档/教程
- 前端
【免费下载链接】en.javascript.info
Modern JavaScript Tutorial
相关推荐
N_m3u8DL-RE 完整指南:一键下载加密 DASH 流
N_m3u8DL RE 完整指南:一键下载加密 DASH 流 你手里只有一个 .mpd 链接,课程却用了 CENC 加密,页面能看不能存,换三个工具都没跑通。N
CLI音视频YARA字符串匹配完全指南:文本、十六进制与正则表达式全解析
YARA字符串匹配完全指南:文本、十六进制与正则表达式全解析 🔪 YARA 是安全领域公认的"模式匹配瑞士军刀"(The pattern matching s
网络安全模式匹配JavaScript 正则表达式实战:用惰性量词精确匹配 HTML 注释(附贪心陷阱解析)
JavaScript 正则表达式实战:用惰性量词精确匹配 HTML 注释(附贪心陷阱解析) 本篇指南以 JavaScript 教程仓库中的实战任务为线索,讲解如
文档/教程前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考