1. 正则表达式字符匹配攻略:要么匹配字符,要么匹配位置
两种模糊匹配:横向模糊和纵向模糊
1.1. 横向模糊匹配:一个正则可匹配的字符串的长度不是固定的,可以是多种情况的
- 其实现的方式是使用量词。譬如 {m,n},表示连续出现最少 m 次,最多 n 次。
- eg: 比如正则 /ab{2,5}c/ 表示匹配这样一个字符串:第一个字符是 "a",接下来是 2 到 5 个字符 "b",最后是字符 "c"。其可视化和测试如下:
1.2. 纵向模糊匹配: 一个正则匹配的字符串,具体到某一位字符时,它可以不是某个确定的字符,可以有多种可能
- 其实现的方式是使用字符组。譬如 [abc],表示该字符是可以字符 "a"、"b"、"c" 中的任何一个。
- 比如 /a[123]b/ 可以匹配如下三种字符串: "a1b"、"a2b"、"a3b"。
字符组
1.3. 范围表示法
- 比如 [123456abcdefGHIJKLM],可以写成 [1-6a-fG-M]。用连字符 - 来省略和简写。
- 需要注意的是,连字符有特殊用途,如果匹配"a"、"-"、"z"中的任意一个,不能写成[a-z],可以写成:[-az]、[za-]、[a-z]。
1.4. 排除字符法
- 其实现的方式是使用反义字符组。譬如 [^abc],表示该字符是排除字符 "a"、"b"、"c" 之外的任意字符。
1.5. 常见的简写形式
- 有了字符组概念,一些常见的符号就好理解了,系统提供了自带的简写方式
贪恋匹配和惰性匹配
1.6. 贪婪匹配
- 正则 /d{2,5}/,表示数字连续出现 2 到 5 次。会匹配 2 位、3 位、4 位、5 位连续数字。但是其是贪婪的,它会尽可能多的匹配。只要在能力范围内,越多越好。
1.7. 惰性匹配
- 正则 /d{2,5}?/ 表示,虽然 2 到 5 次都行,当 2 个就够的时候,就不再往下尝试了。通过在量词后面加个问号就能实现惰性匹配。
1.8. 多选分支
- 一个模式可以实现横向和总想模糊匹配,而多选分之可以支持多个自模式任选其一
- 具体形式:(p1|p2|p3),其中 p1、p2 和 p3 是子模式,用 |(管道符)分隔,表示其中任何之一
- 需要注意的是,分之匹配是惰性的,比如我用 /good|goodbye/,去匹配 "goodbye" 字符串时,结果是 "good"。
2. 正则表达式位置匹配攻略:位置(锚)是相邻字符之间的位置
如何匹配位置,在 ES5 中有 6 个锚:^、$、b、B、(?=p)、(?!p)
- ^(脱字符)匹配开头,在多行匹配中匹配行开头。
- $(美元符号)匹配结尾,在多行匹配中匹配行结尾。
- b 是单词边界,具体就是 w 与 W 之间的位置,也包括 w 与 ^ 之间的位置,和 w 与 $ 之间的位置。
- B 就是 b 的反面的意思,非单词边界。例如在字符串中所有位置中,扣掉 b,剩下的都是 B 的。
- (?=p),其中 p 是一个子模式,即 p 前面的位置,或者说,该位置后面的字符要匹配 p。
- 而 (?!p) 就是 (?=p) 的反面意思
3. 正则表达式括号的作用:分组和分支是括号最直觉的作用
分组
3.1. 我们知道 /a+/ 匹配连续出现的 "a",而要匹配连续出现的 "ab" 时,需要使用 /(ab)+/。其中括号是提供分组功能。
分支结构
3.2. 而在多选分支结构 (p1|p2) 中,此处括号的作用也是不言而喻的,提供了分支表达式的所有可能。
分组引用
3.3. 如:/d{4}-d{2}-d{2}/ 和 /(d{4})-(d{2})-(d{2})/; 后者多了分组的编号。
- 搭配合理的 API,实现更强大的操作。如下可视化正则,多了分组引用的概念
反向引用
3.4. 除了使用相应的 API 来引用分组,也可以在正则本身里引用分组,但只能引用之前出现的分组,即为反向引用。
- 如匹配前后分隔符一致的日期"2017-06-12",可以使用正则 /d{4}(-|/|.)d{2}1d{2}/;其中里面的 1,表示的引用之前的那个分组 (-|/|.)。那么2 和 3 自然理解了,表示第二个和第三个分组。
- 那么 10 表示第十个分组,还是表示1 和 0 呢? 答案是前者,如果要表示后者,请使用(?:1)0 或者 1(?:0)
- 引用不存在的分组,正则匹配时不会报错,只是匹配反向引用的字符本身。
- 分组后面有量词,分组最终捕获的是最后一次的匹配。
3.5. 非捕获括号
- 上文提到的括号,都会匹配他们捕获到的数据,以便后续作为引用,成为捕获型分组或分支。如果想要括号原始功能,此时应使用非捕获括号(?:p) 或 (?:p1|p2|p3)
4. 正则表达式回溯法原理
回溯匹配的概念
4.1. 没有回溯的匹配
- 例如正则是/ab{1,2}c/,如果目标字符串是“abbbc”,其匹配就是没有回溯的,其匹配过程如下:
4.2. 有回溯的匹配
- 如上正则,如果目标字符串是“abbc”,其匹配就是有回溯的,其匹配过程如下,其第6步就是回溯:
贪婪量词和惰性量词
4.3. 贪婪量词
- 比如 b{1,3},因为其是贪婪的,尝试可能的顺序是从多往少的方向去尝试。首先会尝试 "bbb",然后再看整个正则是否能匹配。不能匹配时,吐出一个 "b",即在 "bb" 的基础上,再继续尝试。如果还不行,再吐出一个,再试。如果还不行呢?只能说明匹配失败了。
4.4. 惰性量词
- 惰性量词就是在贪婪量词后面加个问号。表示尽可能少的匹配。
5. 正则表达式的拆分
正则表达式有哪些结构?
5.1. 字符字面量、字符组、量词、锚、分组、选择分支、反向引用,
- 尝试拆分一个正则:/ab?(c|de*)+|fg/
- 由于括号的存在,所以(c|de*)是一个整体
- 而(c|de)存在一个量词,所以 e是一个整体
- 而分支结构|优先级较低,所以 c 是一个整体,de*是一个整体
最终被拆分为 a、b?、(…)+ 和 f、g 两个部分
量词连缀问题
- 如果匹配每个字符为‘a’,‘b’,‘c’任意一个,字符串长度是 3 的倍数,如果正则想当然地写成 /^[abc]{3}+$/,这样会报错,说 + 前面没什么可重复的。而要修改为:
元字符转译问题
5.2. 所谓元字符,就是正则中有特殊含义的字符:^、$、.、*、+、?、|、、/、(、)、[、]、{、}、=、!、:、- ,
- 如果要使用这些元字符,需要对其转义,但并不是所有的字符都需要转义
- 在字符组中的元字符,比如匹配 "[abc]" 和 "{3,5}",可以写成/[abc]/ 或 /[abc]/,/{3,5}/ 或 /{3,5}/
- 其余情况,比如 =、!、:、-、, 等符号,只要不在特殊结构中,并不需要转义;括号需要前后都转义的;于剩下的 ^、$、.、*、+、?、|、、/ 等字符,只要不在字符组内,都需要转义的。
6. 正则表达式的构建和编程
平衡法则
- 匹配预期的字符串
- 不匹配非预期字符串
- 可读性和可维护性
- 效率
正则表达式相关 API
6.1. 用于正则操作的方法,共有 6 个,字符串实例 4 个,正则实例 2 个:
6.2. search 和 match 参数问题,会把参数 string 转为 regexp。
6.3. match 返回结果的格式,与正则对象时候有修饰符 g 有关。没有 g,返回标准格式;有 g,返回所有匹配内容
6.4. exec 比 match 强大,exec 作为对 match 含有 g 匹配内容的补充,包含所有匹配内容的 index 信息
6.5. g 对 exec 和 test 的影响,对于字符串的四个方法,都是从 0 开始的,lastIndex 属性始终保持不变。而对于正则的两个方法,lastIndex 会受 g 的影响
- 注意下面代码中的第三次调用 test,因为这一次尝试匹配,开始从下标 lastIndex,即 3 位置处开始查找,自然就找不到了。
- 如果没有 g,都是从字符串第 0 个位置开始尝试匹配
6.6. test 整体匹配时需要使用 ^ 和 $
6.7. replace 是很强大的。总体来说 replace 有两种使用形式,这是因为它的第二个参数,可以是字符串,也可以是函数。
- 当第二个参数是字符串时,如下的字符有特殊的含义:
6.8. 正则表达式 ES5 有 3 个修饰符