- 快召唤伙伴们来围观吧
- 微博 QQ QQ空间 贴吧
- 文档嵌入链接
- 复制
- 微信扫一扫分享
- 已成功复制到剪贴板
JavaScript正则表达式迷你书
展开查看详情
1 .
2 .JavaScript 正则表达式迷你书 老姚 - v1.0
3 .目录 前言 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 版权说明 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 本书制作用到的工具 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 意见和疑问 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 感谢 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 推荐序 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 导读 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1. 第一章 正则表达式字符匹配攻略 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 1.1. 两种模糊匹配 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 1.1.1. 横向模糊匹配. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 1.1.2. 纵向模糊匹配. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 1.2. 字符组 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 1.2.1. 范围表示法. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 1.2.2. 排除字符组. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 1.2.3. 常见的简写形式. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 1.3. 量词 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 1.3.1. 简写形式. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 1.3.2. 贪婪匹配与惰性匹配. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 1.4. 多选分支 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 1.5. 案例分析 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 1.5.1. 匹配 16 进制颜色值 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 1.5.2. 匹配时间 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 1.5.3. 匹配日期 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 1.5.4. window 操作系统文件路径. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 1.5.5. 匹配 id. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 1.6. 本章小结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 2. 第二章 正则表达式位置匹配攻略 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 2.1. 什么是位置呢? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 2.2. 如何匹配位置呢? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 2.2.1. ^ 和 $ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 2.2.2. \b 和 \B . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 2.2.3. (?=p) 和 (?!p) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.3. 位置的特性 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 2.4. 相关案例 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 2.4.1. 不匹配任何东西的正则 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 2.4.2 数字的千位分隔符表示法. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 2.4.3. 验证密码问题 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 2.5. 本章小结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 3. 第三章 正则表达式括号的作用 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.1. 分组和分支结构 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.1.1. 分组 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.1.2. 分支结构 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.2. 分组引用 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 3.2.1. 提取数据 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4 . 3.2.2. 替换 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.3. 反向引用 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 3.3.1. 括号嵌套怎么办? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 3.3.2. \10 表示什么呢? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 3.3.3. 引用不存在的分组会怎样? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 3.3.4. 分组后面有量词会怎样? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 3.4. 非捕获括号 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 3.5. 相关案例 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 3.5.1. 字符串 trim 方法模拟 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 3.5.2. 将每个单词的首字母转换为大写 . . . . . . . . . . . . . . . . . . . . . . . . . . 34 3.5.3. 驼峰化 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 3.5.4. 中划线化 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 3.5.5. HTML 转义和反转义. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 3.5.6. 匹配成对标签 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 3.6 本章小结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 4. 第四章 正则表达式回溯法原理 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 4.1. 没有回溯的匹配 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 4.2. 有回溯的匹配 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 4.3. 常见的回溯形式 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 4.3.1 贪婪量词. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 4.3.2 惰性量词. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 4.3.3 分支结构. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 4.4. 本章小结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 5. 第五章 正则表达式的拆分 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 5.1. 结构和操作符 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 5.2. 注意要点 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 5.2.1 匹配字符串整体问题. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 5.2.2 量词连缀问题. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 5.2.3 元字符转义问题. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 5.3. 案例分析 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 5.3.1 身份证. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 5.3.2 IPV4 地址 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 5.4. 本章小结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 6. 第六章 正则表达式的构建 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 6.1. 平衡法则 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 6.2. 构建正则前提 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 6.2.1. 是否能使用正则? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 6.2.2. 是否有必要使用正则? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 6.2.3. 是否有必要构建一个复杂的正则? . . . . . . . . . . . . . . . . . . . . . . . . . 54 6.3. 准确性 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 6.3.1. 匹配固定电话 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 6.3.2. 匹配浮点数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 6.4. 效率 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 6.4.1. 使用具体型字符组来代替通配符,来消除回溯 . . . . . . . . . . . . . . . . . . . . 59 6.4.2. 使用非捕获型分组 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 6.4.3. 独立出确定字符 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
5 . 6.4.4. 提取分支公共部分 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 6.4.5. 减少分支的数量,缩小它们的范围 . . . . . . . . . . . . . . . . . . . . . . . . . 61 6.5. 本章小结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 7. 第七章 正则表达式编程 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 7.1. 正则表达式的四种操作 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 7.1.1. 验证 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 7.1.2. 切分 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 7.1.3. 提取 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 7.1.4. 替换 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 7.2. 相关 API 注意要点 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 7.2.1. search 和 match 的参数问题 . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 7.2.2. match 返回结果的格式问题 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 7.2.3. exec 比 match 更强大 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 7.2.4. 修饰符 g,对 exex 和 test 的影响 . . . . . . . . . . . . . . . . . . . . . . . . 67 7.2.5. test 整体匹配时需要使用 ^ 和 $ . . . . . . . . . . . . . . . . . . . . . . . . . 68 7.2.6. split 相关注意事项 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 7.2.7. replace 是很强大的 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 7.2.8. 使用构造函数需要注意的问题 . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 7.2.9. 修饰符 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 7.2.10. source 属性 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 7.2.11. 构造函数属性. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 7.3. 真实案例 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 7.3.1. 使用构造函数生成正则表达式 . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 7.3.2. 使用字符串保存数据 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 7.3.3. if 语句中使用正则替代 && . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 7.3.4. 使用强大的 replace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 7.3.5. 综合运用 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 7.4. 本章小结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 后记 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 速查表 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 术语中英文对照表 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 参考书目 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
6 . JavaScript 正则表达式迷你书 前言 版权说明 首先感谢看到这本《JavaScript 正则表达式迷你书》的读者朋友,但愿此书不会浪费你的宝贵时间。 此书是一本免费的书籍,您可以随便传播,但不能用于商业。 当读完后,如果你觉得此书对学习正则表达式有所帮助,慷慨如你,可以扫描下面的二维码赞赏一下。 不强求的,本来就是免费的嘛。说真的,我也不曾妄想,靠此就可以发大财,从此走上人生小巅峰。 当然,我也在想,如果能得到读者的认可,自己的小心脏肯定会砰砰的。 本书制作用到的工具 本书采用Asciidoc格式编写。 使用 Asciidoctor 构建本书电子版。 教程: • 语法教程 • 转换PDF教程 书中用的可视化图片是由 Regulex 正则表达式可视化工具生成。 其余图片是由 Processon 制作。 前言 | 第 1 页
7 . JavaScript 正则表达式迷你书 意见和疑问 如果有意见或者问题的话,可以直接通过以下方式联系到我。 • 前端网 • 知乎 • 掘金 • qdlaoyao@126.com 后续版本更新,会在这里更新: 《JavaScript 正则表达式迷你书》 感谢 由于本书是由个人文章修改而成,感谢各平台读者的支持。 感谢湖山,是他说我该把这些东西写出来的。 感谢小不,他在多方面给予了我帮助,封面是他设计的。 感谢小鱼二,他对全书进行了仔细地校对,并提出了相应的修改意见。 感谢丹迪的支持,他为我设计了多个封面,风格比较前卫,留给后续版本。 最后,尤其要感谢各位大佬帮我写的推荐序。他们的名字不分先后如下:大漠穷秋、小鱼二、Jack Lo、程序猿DD、江湖人称向前兄、文蔺、_周末、Dark_Night。 推荐序 正则表达式一直是我的一个痛点,很多人肯定也跟我一样存在类似的情况。但是正则表达式的使用范 围非常广泛,尤其在表单校验这个场景下更是不可或缺。这本小书用一个一个的小例子深入浅出地示 范了正则表达式的典型用法,值得一读。 — 大漠穷秋 我连续看了老姚在专栏的正则系列的前三篇,毫不犹豫就打赏了,而且顺藤摸瓜认识了老姚,没想到 老姚对学习本身研究颇深,当晚第一次聊天就是半宿。在本系列中,我印象最深的两句话是“正则表 达式是匹配模式,要么匹配字符,要么匹配位置”和“对于位置的理解,我们可以理解成空字符”, 这两句话可谓是醍醐灌顶,通过数字的千位分隔符这个例子把之前一直搞不清楚的先行断言彻底搞懂 了。真是佩服老姚的理解和讲解能力。相信我,通读迷你小书,可以让你真正理解正则并在工作中不 怕读,不怕写正则。让正则成为你开发中的一把利器。 — 小鱼二 前言 | 第 2 页
8 . JavaScript 正则表达式迷你书 这是一本由浅入深且环环相扣的正则书籍,花了两天的碎片时间(8h)看完了,得益于老姚程序员的 逻辑性以及娴熟的文字表达能力,原本枯燥晦涩的正则知识,变得清晰且有迹可循! — Jack Lo 老姚编写的JavaScript正则表达式系列文章通俗易通,虽然示例以JavaScript编写,但是对于正则表 达式的学习通用于其他语言。所以,不论您是前端还是后端工程师,通过阅读此迷你书都能获益。最 后,感谢老姚能够写出这一系列文章,让大家能够更轻松的理解和使用正则表达式。 — 程序猿DD 正则表达式是通用的技能,基础的东西永远绕不开。能在实战中进行总结,并形成专题,更是一种值 得学习的方式。也就几个小时的时间,看完这本图文并茂、贴近实战的教程之后,你会发现自己的代 码其实还可以再精简下。 — 江湖人称向前兄 本书规则、案例、原理兼备,讲解透彻,是一本不可多得的正则表达式入门、进阶资料。无论你是初 入门的小白,还是有经验的程序员,都能从这本书学到很多东西。这可能是我读过的最好懂的一本正 则教程。感谢作者老姚的工作。 — 文蔺 良师易得,益友难求。工作中得到了老姚的很多帮助,很是感谢。最近拜读了老姚的正则表达式一书 ,受益匪浅,从每次遇到正则问题,从百度到自己书写,都离不开书中的知识。并且此书通俗易懂, 条理清晰,每次阅读都会得到新的收获。感谢老姚,支持你,加油! — _周末 对于正则的知识,之前看得总是零零碎碎的,没有好好地去系统学习过,所以在方面知识体系相对薄 弱。通过这本正则迷你书,总算有一个清晰掌握。一直以来比较关注作者的笔记和文章,自身在JS的 成长上也受益于姚哥帮助,感谢他对这本书的付出,希望这本迷你书能帮助更多想学习正则的同学。 — Dark_Night 前言 | 第 3 页
9 . JavaScript 正则表达式迷你书 导读 亲爱的读者朋友,如果你打开了这本书,说明你跟我一样,对正则很感兴趣。 想必你也了解正则的重要性。在我看来,正则表达式是衡量程序员水平的一个侧面标准。 本书的目的,是希望所有认真读完的朋友们,能真正地学会并应用正则表达式。 本书内容共有七章,完整地讨论了 JavaScript 语言的正则表达式方方面面。 具体章节如下: • 第一章 正则表达式字符匹配攻略 • 第二章 正则表达式位置匹配攻略 • 第三章 正则表达式括号的作用 • 第四章 正则表达式回溯法原理 • 第五章 正则表达式的拆分 • 第六章 正则表达式的构建 • 第七章 正则表达式编程 下面简单地说说每一章都讨论了什么? 正则是匹配模式,要么匹配字符,要么匹配位置。 第一章和第二章以这个角度去讲解了正则表达式的基础。 在正则可以使用括号捕获数据,要么在 API 中进行分组引用,要么在正则里进行反向引用。 这是第三章的主题,讲解了正则表达式中括号的作用。 学习正则,是需要了解其匹配原理的。 第四章,讲解了正则表达式的回溯法原理。 另外在第六章最后一节,也讲解了正则的表达式的整体工作原理。 不仅能看懂别人的正则,还要自己会写正则。 第五章,是从读的角度,去拆分一个正则表达式,而第六章是从写的角度,去构建一个正则表达式。 学习正则,是为了在真实世界里应用的。 前言 | 第 4 页
10 . JavaScript 正则表达式迷你书 第七章讲解了正则的用法,和相关 API 需要注意的地方。 虽然你可以直接阅读你想了解的任何一章,但我还是建议从头到尾地完整阅读。本书是迷你书,不厚的。 最好阅读两遍。第一遍,不求甚解地快速阅读一遍。阅读过程中遇到的问题不妨记录下来,也许阅读完毕后 就能解决很多。 然后有时间的话,再带着问题去精读第二遍。 深呼吸,开始我们的正则表达式旅程吧。 我在终点等你。 前言 | 第 5 页
11 . JavaScript 正则表达式迷你书 1. 第一章 正则表达式字符匹配攻略 正则表达式是匹配模式,要么匹配字符,要么匹配位置。 请记住这句话。 然而关于正则如何匹配字符的学习,大部分人都觉得这块比较杂乱。 毕竟元字符太多了,看起来没有系统性,不好记。本章就解决这个问题。 内容包括: • 两种模糊匹配 • 字符组 • 量词 • 分支结构 • 案例分析 1.1. 两种模糊匹配 如果正则只有精确匹配是没多大意义的,比如 /hello/,也只能匹配字符串中的 "hello" 这个子串。 var regex = /hello/; console.log( regex.test("hello") ); // => true 正则表达式之所以强大,是因为其能实现模糊匹配。 而模糊匹配,有两个方向上的“模糊”:横向模糊和纵向模糊。 1.1.1. 横向模糊匹配 横向模糊指的是,一个正则可匹配的字符串的长度不是固定的,可以是多种情况的。 其实现的方式是使用量词。譬如 {m,n},表示连续出现最少 m 次,最多 n 次。 比如正则 /ab{2,5}c/ 表示匹配这样一个字符串:第一个字符是 "a",接下来是 2 到 5 个字符 "b",最后 是字符 "c"。 其可视化形式如下: 1. 第一章 正则表达式字符匹配攻略 | 第 6 页
12 . JavaScript 正则表达式迷你书 测试如下: var regex = /ab{2,5}c/g; var string = "abc abbc abbbc abbbbc abbbbbc abbbbbbc"; console.log( string.match(regex) ); // => ["abbc", "abbbc", "abbbbc", "abbbbbc"] 案例中用的正则是 /ab{2,5}c/g,其中 g 是正则的一个修饰符。表示全局匹配,即,在目 NOTE 标字符串中按顺序找到满足匹配模式的所有子串,强调的是“所有”,而不只是“第一个” 。g 是单词 global 的首字母。 1.1.2. 纵向模糊匹配 纵向模糊指的是,一个正则匹配的字符串,具体到某一位字符时,它可以不是某个确定的字符,可以有多种 可能。 其实现的方式是使用字符组。譬如 [abc],表示该字符是可以字符 "a"、"b"、"c" 中的任何一个。 比如 /a[123]b/ 可以匹配如下三种字符串: "a1b"、"a2b"、"a3b"。 其可视化形式如下: 测试如下: var regex = /a[123]b/g; var string = "a0b a1b a2b a3b a4b"; console.log( string.match(regex) ); // => ["a1b", "a2b", "a3b"] 以上就是本章讲的主体内容,只要掌握横向和纵向模糊匹配,就能解决很大部分正则匹配问题。 接下来,我们将具体展开来说。 1.2. 字符组 需要强调的是,虽叫字符组(字符类),但只是其中一个字符。 例如 [abc],表示匹配一个字符,它可以是 "a"、"b"、"c" 之一。 1. 第一章 正则表达式字符匹配攻略 | 第 7 页
13 . JavaScript 正则表达式迷你书 1.2.1. 范围表示法 如果字符组里的字符特别多的话,怎么办?可以使用范围表示法。 比如 [123456abcdefGHIJKLM],可以写成 [1-6a-fG-M]。用连字符 - 来省略和简写。 因为连字符有特殊用途,那么要匹配 "a"、"-"、"z" 这三者中任意一个字符,该怎么做呢? 不能写成 [a-z],因为其表示小写字符中的任何一个字符。 可以写成如下的方式:[-az] 或 [az-] 或 [a\-z]。 即要么放在开头,要么放在结尾,要么转义。总之不会让引擎认为是范围表示法就行了。 1.2.2. 排除字符组 纵向模糊匹配,还有一种情形就是,某位字符可以是任何东西,但就不能是 "a"、"b"、"c"。 此时就是排除字符组(反义字符组)的概念。例如 [^abc],表示是一个除 "a"、"b"、"c"之外的任意一个字 符。字符组的第一位放 ^(脱字符),表示求反的概念。 当然,也有相应的范围表示法。 1.2.3. 常见的简写形式 有了字符组的概念后,一些常见的符号我们也就理解了。因为它们都是系统自带的简写形式。 字符组 具体含义 表示 [0-9]。表示是一位数字。 \d 记忆方式:其英文是 digit(数字)。 \D 表示 [^0-9]。表示除数字外的任意字符。 表示 [0-9a-zA-Z_]。表示数字、大小写字母和下划线。 \w 记忆方式:w 是 word 的简写,也称单词字符。 \W 表示 [^0-9a-zA-Z_]。非单词字符。 表示 [ \t\v\n\r\f]。表示空白符,包括空格、水平制表符、垂直制表符、换行符、回车符、换页 \s 符。 记忆方式:s 是 space 的首字母,空白符的单词是 white space。 \S 表示 [^ \t\v\n\r\f]。 非空白符。 表示 [^\n\r\u2028\u2029]。通配符,表示几乎任意字符。换行符、回车符、行分隔符和段分隔符 . 除外。 记忆方式:想想省略号 … 中的每个点,都可以理解成占位符,表示任何类似的东西。 如果要匹配任意字符怎么办?可以使用 [\d\D]、[\w\W]、[\s\S] 和 [^] 中任何的一个。 以上各字符组对应的可视化形式是: 1. 第一章 正则表达式字符匹配攻略 | 第 8 页
14 . JavaScript 正则表达式迷你书 1.3. 量词 量词也称重复。掌握 {m,n} 的准确含义后,只需要记住一些简写形式。 1.3.1. 简写形式 量词 具体含义 {m,} 表示至少出现 m 次。 {m} 等价于 {m,m},表示出现 m 次。 等价于 {0,1},表示出现或者不出现。 ? 记忆方式:问号的意思表示,有吗? 等价于 {1,},表示出现至少一次。 + 记忆方式:加号是追加的意思,得先有一个,然后才考虑追加。 等价于 {0,},表示出现任意次,有可能不出现。 * 记忆方式:看看天上的星星,可能一颗没有,可能零散有几颗,可能数也数不过来。 以上量词对应的可视化形式是: 1.3.2. 贪婪匹配与惰性匹配 看如下的例子: var regex = /\d{2,5}/g; var string = "123 1234 12345 123456"; console.log( string.match(regex) ); // => ["123", "1234", "12345", "12345"] 其中正则 /\d{2,5}/,表示数字连续出现 2 到 5 次。会匹配 2 位、3 位、4 位、5 位连续数字。 但是其是贪婪的,它会尽可能多的匹配。你能给我 6 个,我就要 5 个。你能给我 3 个,我就要 3 个。 反正只要在能力范围内,越多越好。 我们知道有时贪婪不是一件好事(请看文章最后一个例子)。而惰性匹配,就是尽可能少的匹配: var regex = /\d{2,5}?/g; var string = "123 1234 12345 123456"; console.log( string.match(regex) ); // => ["12", "12", "34", "12", "34", "12", "34", "56"] 其中 /\d{2,5}?/ 表示,虽然 2 到 5 次都行,当 2 个就够的时候,就不再往下尝试了。 1. 第一章 正则表达式字符匹配攻略 | 第 9 页
15 . JavaScript 正则表达式迷你书 通过在量词后面加个问号就能实现惰性匹配,因此所有惰性匹配情形如下: 惰性量词 贪婪量词 {m,n}? {m,n} {m,}? {m,} ?? ? +? + *? * TIP 对惰性匹配的记忆方式是:量词后面加个问号,问一问你知足了吗,你很贪婪吗? 以上惰性量词对应的可视化形式是: 1.4. 多选分支 一个模式可以实现横向和纵向模糊匹配。而多选分支可以支持多个子模式任选其一。 具体形式如下:(p1|p2|p3),其中 p1、p2 和 p3 是子模式,用 |(管道符)分隔,表示其中任何之一。 例如要匹配字符串 "good" 和 "nice" 可以使用 /good|nice/。 可视化形式如下: 测试如下: var regex = /good|nice/g; var string = "good idea, nice try."; console.log( string.match(regex) ); // => ["good", "nice"] 但有个事实我们应该注意,比如我用 /good|goodbye/,去匹配 "goodbye" 字符串时,结果是 "good": 1. 第一章 正则表达式字符匹配攻略 | 第 10 页
16 . JavaScript 正则表达式迷你书 var regex = /good|goodbye/g; var string = "goodbye"; console.log( string.match(regex) ); // => ["good"] 而把正则改成 /goodbye|good/,结果是: var regex = /goodbye|good/g; var string = "goodbye"; console.log( string.match(regex) ); // => ["goodbye"] 也就是说,分支结构也是惰性的,即当前面的匹配上了,后面的就不再尝试了。 1.5. 案例分析 匹配字符,无非就是字符组、量词和分支结构的组合使用罢了。 下面找几个例子演练一下(其中,每个正则并不是只有唯一写法): 1.5.1. 匹配 16 进制颜色值 要求匹配: #ffbbad #Fc01DF #FFF #ffE 分析: 表示一个 16 进制字符,可以用字符组 [0-9a-fA-F]。 其中字符可以出现 3 或 6 次,需要是用量词和分支结构。 使用分支结构时,需要注意顺序。 正则如下: var regex = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g; var string = "#ffbbad #Fc01DF #FFF #ffE"; console.log( string.match(regex) ); // => ["#ffbbad", "#Fc01DF", "#FFF", "#ffE"] 1. 第一章 正则表达式字符匹配攻略 | 第 11 页
17 . JavaScript 正则表达式迷你书 其可视化形式: 1.5.2. 匹配时间 以 24 小时制为例。 要求匹配: 23:59 02:07 分析: 共 4 位数字,第一位数字可以为 [0-2]。 当第 1 位为 "2" 时,第 2 位可以为 [0-3],其他情况时,第 2 位为 [0-9]。 第 3 位数字为 [0-5],第4位为 [0-9]。 正则如下: var regex = /^([01][0-9]|[2][0-3]):[0-5][0-9]$/; console.log( regex.test("23:59") ); console.log( regex.test("02:07") ); // => true // => true 1. 第一章 正则表达式字符匹配攻略 | 第 12 页
18 . JavaScript 正则表达式迷你书 NOTE 正则中使用了 ^ 和 $,分别表示字符串开头和结尾。具体详细请参考第二章。 如果也要求匹配 "7:9",也就是说时分前面的 "0" 可以省略。 此时正则变成: var regex = /^(0?[0-9]|1[0-9]|[2][0-3]):(0?[0-9]|[1-5][0-9])$/; console.log( regex.test("23:59") ); console.log( regex.test("02:07") ); console.log( regex.test("7:9") ); // => true // => true // => true 其可视化形式: 1.5.3. 匹配日期 比如 yyyy-mm-dd 格式为例。 要求匹配: 2017-06-10 分析: 年,四位数字即可,可用 [0-9]{4}。 月,共 12 个月,分两种情况 "01"、"02"、…、"09" 和 "10"、"11"、"12",可用 (0[1-9]|1[0-2])。 日,最大 31 天,可用 (0[1-9]|[12][0-9]|3[01])。 1. 第一章 正则表达式字符匹配攻略 | 第 13 页
19 . JavaScript 正则表达式迷你书 正则如下: var regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/; console.log( regex.test("2017-06-10") ); // => true 其可视化形式: 1.5.4. window 操作系统文件路径 要求匹配: F:\study\javascript\regex\regular expression.pdf F:\study\javascript\regex\ F:\study\javascript F:\ 分析: 整体模式是: 盘符:\文件夹\文件夹\文件夹\ 其中匹配 "F:\",需要使用 [a-zA-Z]:\\,其中盘符不区分大小写,注意 \ 字符需要转义。 文件名或者文件夹名,不能包含一些特殊字符,此时我们需要排除字符组 [^\\:*<>|"?\r\n/] 来表示合法 字符。 另外它们的名字不能为空名,至少有一个字符,也就是要使用量词 +。因此匹配 文件夹\,可用 [^\\:*<>|"?\r\n/]+\\。 另外 文件夹\,可以出现任意次。也就是 ([^\\:*<>|"?\r\n/]+\\)*。其中括号表示其内部正则是一个整 1. 第一章 正则表达式字符匹配攻略 | 第 14 页
20 . JavaScript 正则表达式迷你书 体。具体详细请参考第三章。 路径的最后一部分可以是 文件夹,没有 \,因此需要添加 ([^\\:*<>|"?\r\n/]+)?。 最后拼接成了一个看起来比较复杂的正则: var regex = /^[a-zA-Z]:\\([^\\:*<>|"?\r\n/]+\\)*([^\\:*<>|"?\r\n/]+)?$/; console.log( regex.test("F:\\study\\javascript\\regex\\regular expression.pdf") ); console.log( regex.test("F:\\study\\javascript\\regex\\") ); console.log( regex.test("F:\\study\\javascript") ); console.log( regex.test("F:\\") ); // => true // => true // => true // => true 其中,在JavaScript 中字符串要表示字符 \ 时,也需要转义。 其可视化形式: 1.5.5. 匹配 id 要求从 <div id="container" class="main"></div> 提取出 id="container"。 可能最开始想到的正则是: var regex = /id=".*"/ var string = '<div id="container" class="main"></div>'; console.log(string.match(regex)[0]); // => id="container" class="main" 其可视化形式: 1. 第一章 正则表达式字符匹配攻略 | 第 15 页
21 . JavaScript 正则表达式迷你书 因为 . 是通配符,本身就匹配双引号的,而量词 * 又是贪婪的,当遇到 container 后面双引号时,是不会 停下来,会继续匹配,直到遇到最后一个双引号为止。 解决之道,可以使用惰性匹配: var regex = /id=".*?"/ var string = '<div id="container" class="main"></div>'; console.log(string.match(regex)[0]); // => id="container" 当然,这样也会有个问题。效率比较低,因为其匹配原理会涉及到“回溯”这个概念(这里也只是顺便提一 下,第四章会详细说明)。可以优化如下: var regex = /id="[^"]*"/ var string = '<div id="container" class="main"></div>'; console.log(string.match(regex)[0]); // => id="container" 1.6. 本章小结 掌握字符组和量词就能解决大部分常见的情形,也就是说,当你会了这二者,JavaScript 正则算是入门了。 1. 第一章 正则表达式字符匹配攻略 | 第 16 页
22 . JavaScript 正则表达式迷你书 2. 第二章 正则表达式位置匹配攻略 正则表达式是匹配模式,要么匹配字符,要么匹配位置。请记住这句话。 然而大部分人学习正则时,对于匹配位置的重视程度没有那么高。 本章讲讲正则匹配位置的相关知识点。 内容包括: • 什么是位置? • 如何匹配位置? • 位置的特性 • 几个应用实例分析 2.1. 什么是位置呢? 位置(锚)是相邻字符之间的位置。比如,下图中箭头所指的地方: 2.2. 如何匹配位置呢? 在 ES5 中,共有 6 个锚: ^、$、\b、\B、(?=p)、(?!p) 相应的可视化形式是: 2. 第二章 正则表达式位置匹配攻略 | 第 17 页
23 . JavaScript 正则表达式迷你书 2.2.1. ^ 和 $ ^(脱字符)匹配开头,在多行匹配中匹配行开头。 $(美元符号)匹配结尾,在多行匹配中匹配行结尾。 比如我们把字符串的开头和结尾用 "#" 替换(位置可以替换成字符的!): var result = "hello".replace(/^|$/g, '#'); console.log(result); // => "#hello#" 多行匹配模式(即有修饰符 m)时,二者是行的概念,这一点需要我们注意: var result = "I\nlove\njavascript".replace(/^|$/gm, '#'); console.log(result); /* #I# #love# #javascript# */ 2.2.2. \b 和 \B \b 是单词边界,具体就是 \w 与 \W 之间的位置,也包括 \w 与 ^ 之间的位置,和 \w 与 $ 之间的位置。 比如考察文件名 "[JS] Lesson_01.mp4" 中的 \b,如下: var result = "[JS] Lesson_01.mp4".replace(/\b/g, '#'); console.log(result); // => "[#JS#] #Lesson_01#.#mp4#" 为什么是这样呢?这需要仔细看看。 首先,我们知道,\w 是字符组 [0-9a-zA-Z_] 的简写形式,即 \w 是字母数字或者下划线的中任何一个字 符。而 \W 是排除字符组 [^0-9a-zA-Z_] 的简写形式,即 \W 是 \w 以外的任何一个字符。 此时我们可以看看 "[#JS#] #Lesson_01#.#mp4#" 中的每一个井号 ,是怎么来的。 • 第 1 个,两边字符是 "[" 与 "J",是 \W 与 \w 之间的位置。 • 第 2 个,两边字符是 "S" 与 "]",也就是 \w 与 \W 之间的位置。 • 第 3 个,两边字符是空格与 "L",也就是 \W 与 \w 之间的位置。 • 第 4 个,两边字符是 "1" 与 ".",也就是 \w 与 \W 之间的位置。 2. 第二章 正则表达式位置匹配攻略 | 第 18 页
24 . JavaScript 正则表达式迷你书 • 第 5 个,两边字符是 "." 与 "m",也就是 \W 与 \w之间的位置。 • 第 6 个,位于结尾,前面的字符 "4" 是 \w,即 \w 与 $ 之间的位置。 知道了 \b 的概念后,那么 \B 也就相对好理解了。 \B 就是 \b 的反面的意思,非单词边界。例如在字符串中所有位置中,扣掉 \b,剩下的都是 \B 的。 具体说来就是 \w 与 \w、 \W 与 \W、^ 与 \W,\W 与 $ 之间的位置。 比如上面的例子,把所有 \B 替换成 "#": var result = "[JS] Lesson_01.mp4".replace(/\B/g, '#'); console.log(result); // => "#[J#S]# L#e#s#s#o#n#_#0#1.m#p#4" 2.2.3. (?=p) 和 (?!p) (?=p),其中 p 是一个子模式,即 p 前面的位置,或者说,该位置后面的字符要匹配 p。 比如 (?=l),表示 "l" 字符前面的位置,例如: var result = "hello".replace(/(?=l)/g, '#'); console.log(result); // => "he#l#lo" 而 (?!p) 就是 (?=p) 的反面意思,比如: var result = "hello".replace(/(?!l)/g, '#'); console.log(result); // => "#h#ell#o#" 二者的学名分别是 positive lookahead 和 negative lookahead。 中文翻译分别是正向先行断言和负向先行断言。 ES5 之后的版本,会支持 positive lookbehind 和 negative lookbehind。 具体是 (?<=p) 和 (?<!p)。 也有书上把这四个东西,翻译成环视,即看看右边和看看左边。 但一般书上,没有很好强调这四者是个位置。 比如 (?=p),一般都理解成:要求接下来的字符与 p 匹配,但不能包括 p 匹配的那些字符。 2. 第二章 正则表达式位置匹配攻略 | 第 19 页
25 . JavaScript 正则表达式迷你书 而在本人看来,(?=p) 就与 ^ 一样好理解,就是 p 前面的那个位置。 2.3. 位置的特性 对于位置的理解,我们可以理解成空字符 ""。 比如 "hello" 字符串等价于如下的形式: "hello" == "" + "h" + "" + "e" + "" + "l" + "" + "l" + "o" + ""; 也等价于: "hello" == "" + "" + "hello" 因此,把 /\^hello$/ 写成 /^^hello$$$/,是没有任何问题的: var result = /^^hello$$$/.test("hello"); console.log(result); // => true 甚至可以写成更复杂的: var result = /(?=he)^^he(?=\w)llo$\b\b$/.test("hello"); console.log(result); // => true 也就是说字符之间的位置,可以写成多个。 TIP 把位置理解空字符,是对位置非常有效的理解方式。 2.4. 相关案例 2.4.1. 不匹配任何东西的正则 让你写个正则不匹配任何东西 easy,/.^/。 因为此正则要求只有一个字符,但该字符后面是开头,而这样的字符串是不存在的。 2. 第二章 正则表达式位置匹配攻略 | 第 20 页
26 . JavaScript 正则表达式迷你书 2.4.2 数字的千位分隔符表示法 比如把 "12345678",变成 "12,345,678"。 可见是需要把相应的位置替换成 ","。 思路是什么呢? 2.4.2.1. 弄出最后一个逗号 使用 (?=\d{3}$) 就可以做到: var result = "12345678".replace(/(?=\d{3}$)/g, ',') console.log(result); // => "12345,678" 其中,(?=\d{3}$) 匹配 \d{3}$ 前面的位置。而 \d{3}$ 匹配的是目标字符串最后那 3 位数字。 2.4.2.2. 弄出所有的逗号 因为逗号出现的位置,要求后面 3 个数字一组,也就是 \d{3} 至少出现一次。 此时可以使用量词 +: var result = "12345678".replace(/(?=(\d{3})+$)/g, ',') console.log(result); // => "12,345,678" 2.4.2.3. 匹配其余案例 写完正则后,要多验证几个案例,此时我们会发现问题: var result = "123456789".replace(/(?=(\d{3})+$)/g, ',') console.log(result); // => ",123,456,789" 因为上面的正则,仅仅表示把从结尾向前数,一但是 3 的倍数,就把其前面的位置替换成逗号。因此才会出 现这个问题。 怎么解决呢?我们要求匹配的到这个位置不能是开头。 我们知道匹配开头可以使用 ^,但要求这个位置不是开头怎么办? easy,(?!^),你想到了吗?测试如下: 2. 第二章 正则表达式位置匹配攻略 | 第 21 页
27 . JavaScript 正则表达式迷你书 var regex = /(?!^)(?=(\d{3})+$)/g; var result = "12345678".replace(regex, ',') console.log(result); // => "12,345,678" result = "123456789".replace(regex, ','); console.log(result); // => "123,456,789" 2.4.2.4. 支持其他形式 如果要把 "12345678 123456789" 替换成 "12,345,678 123,456,789"。 此时我们需要修改正则,把里面的开头 ^ 和结尾 $,修改成 \b: var string = "12345678 123456789", regex = /(?!\b)(?=(\d{3})+\b)/g; var result = string.replace(regex, ',') console.log(result); // => "12,345,678 123,456,789" 其中 (?!\b) 怎么理解呢? 要求当前是一个位置,但不是 \b 前面的位置,其实 (?!\b) 说的就是 \B。 因此最终正则变成了:/\B(?=(\d{3})+\b)/g。 可视化形式是: 2.4.2.5. 格式化 千分符表示法一个常见的应用就是货币格式化。 比如把下面的字符串: 2. 第二章 正则表达式位置匹配攻略 | 第 22 页
28 . JavaScript 正则表达式迷你书 1888 格式化成: $ 1888.00 有了前面的铺垫,我们很容实现如下: function format (num) { return num.toFixed(2).replace(/\B(?=(\d{3})+\b)/, ",").replace(/^/, "$$ "); }; console.log( format(1888) ); // => "$ 1,888.00" 2.4.3. 验证密码问题 密码长度 6-12 位,由数字、小写字符和大写字母组成,但必须至少包括 2 种字符。 此题,如果写成多个正则来判断,比较容易。但要写成一个正则就比较困难。 那么,我们就来挑战一下。看看我们对位置的理解是否深刻。 2.4.3.1. 简化 不考虑“但必须至少包括 2 种字符”这一条件。我们可以容易写出: var regex = /^[0-9A-Za-z]{6,12}$/; 2.4.3.2. 判断是否包含有某一种字符 假设,要求的必须包含数字,怎么办?此时我们可以使用 (?=.*[0-9]) 来做。 因此正则变成: var regex = /(?=.*[0-9])^[0-9A-Za-z]{6,12}$/; 2.4.3.3. 同时包含具体两种字符 比如同时包含数字和小写字母,可以用 (?=.[0-9])(?=.[a-z]) 来做。 因此正则变成: var regex = /(?=.*[0-9])(?=.*[a-z])^[0-9A-Za-z]{6,12}$/; 2. 第二章 正则表达式位置匹配攻略 | 第 23 页
29 . JavaScript 正则表达式迷你书 2.4.3.4. 解答 我们可以把原题变成下列几种情况之一: • 同时包含数字和小写字母 • 同时包含数字和大写字母 • 同时包含小写字母和大写字母 • 同时包含数字、小写字母和大写字母 • 以上的 4 种情况是或的关系(实际上,可以不用第 4 条)。 最终答案是: var regex = /((?=.*[0-9])(?=.*[a-z])|(?=.*[0-9])(?=.*[A-Z])|(?=.*[a-z])(?=.*[A- Z]))^[0-9A-Za-z]{6,12}$/; console.log( regex.test("1234567") ); // false 全是数字 console.log( regex.test("abcdef") ); // false 全是小写字母 console.log( regex.test("ABCDEFGH") ); // false 全是大写字母 console.log( regex.test("ab23C") ); // false 不足6位 console.log( regex.test("ABCDEF234") ); // true 大写字母和数字 console.log( regex.test("abcdEF234") ); // true 三者都有 可视化形式是: 2.4.3.5. 解惑 上面的正则看起来比较复杂,只要理解了第二步,其余就全部理解了。 /(?=.*[0-9])^[0-9A-Za-z]{6,12}$/ 对于这个正则,我们只需要弄明白 (?=.*[0-9])^ 即可。 2. 第二章 正则表达式位置匹配攻略 | 第 24 页