正则表达式
正则表达式用于1.匹配字符,或者2.匹配位置
1.字符匹配攻略
- 横向与纵向匹配
{m,n}匹配m到n长度的字符/ab{2,4}c/匹配 a 和 c 中间2到4个b
[abc]匹配abc中任一字符/ab[123]c/匹配 ab 和 c 中间出现1、2或者3
- 纵向匹配的扩展:字符组——一个字符的集合
-表示范围/[a-d2-5D-I]/是[abcd2345DHI]的省略简写模式- 转义问题:
\-用来匹配-字符本身
^表示取反/[^abc]/可以匹配x但不匹配a、b、c- Q:^ 只能放在纵向匹配方括号中吗?
- 常见缩写——大小写互为补集
\d=[0-9]::Digit::\D=[^0-9]\w=[0-9a-zA-Z_]数字、大小写字母和下划线 ::Word::\W=[^0-9a-zA-Z]\s=[\t\v\n\r\f]表示空白符,包括空格、水平制表符- 换行符、回车符、换页符::Space::
\S.=[^\n\r\u2028\u2029]通配符,几乎匹配任何字符,换行符、回车符、行分隔符和段分隔符除外[\d\D] [\w\W] [\s\S] [^]可以表示匹配任何字符
- 横向匹配的扩展:量词
- 简写
{m,}最少 m 次{m}精确的 m 次?={0,1}出现一次或0次+={1,}最少出现一次*={0,}可以不出现或出现不限次数
- 贪婪匹配和惰性匹配
- 在量词后边加
?可以改为惰性匹配 - Q:差别不是特别清楚 贪婪匹配会匹配出尽可能长的字符串,惰性则是尽可能短的,符合匹配就返回
/\d{2,4}/g匹配123 1234 12345 123456结果:123 1234 1234 1234 56/\d{2,4}?/g匹配123 1234 12345 123456结果:12 12 34 12 34 12 34 56/\d+/匹配123 2结果:123 2/\d+?/匹配123 2结果:1 2 3 2
- 在量词后边加
- 简写
- 多选分支
|管道符可以在多个模式中任选其一匹配/good|goodbye/匹配到good就会返回,而不会匹配到goodbye,所以管道符号会短路,有匹配到就不会继续匹配
2.位置匹配攻略
理解「位置」:可以认为位置是字符串中字符与字符之间的“空字符”
例如
1 | "hello" === "" + "hello" + "" |
匹配位置就是匹配这些空字符串,这些空字符串可以做替换
^$匹配开头结尾- 把字符串的开头/结尾替换成#
'hello'.replace(/^|$/g, '#') // '#hello#' - 多行匹配时,两个字符是行的开头结尾
1
2
3
4
5
6// g: 全局匹配,m: 多行匹配
'hello\nworld'.replace(/^|$/gm, '#')
/*
* #hello#
* #world#
*/
- 把字符串的开头/结尾替换成#
\b匹配单词边界,\w与\W或^或$的边界 ::Boundary::- 虽然被称为“单词”,但
\w代表数字字母下划线,不仅仅是字母 - 一个文件名为”[JS] Lesson_01.mp4”,其单词边界为
1
2'[JS] Lesson_01.mp4'.replace(/\b/g, '#')
// [#JS#] #Lesson_01#.#mp4#
- 虽然被称为“单词”,但
\B取反1
2'[JS] Lesson_01.mp4'.replace(/\B/g, '#')
// #[J#S]# L#e#s#s#o#n#_#0#1.m#p#4(?=p)匹配p左边的位置,p表示一个子模式- positive lookahead,正向先行断言
- 匹配 l 之前的位置
1
2'hello'.replace(/(?=l)/g, '#')
// he#l#lo
(?!p)匹配与(?=p)取反- negative lookahead,负向先行断言
ES6 还支持
(?<=p)和(?<!p)表示匹配右边的位置
案例:数字的千分位分隔符匹配法
要求把 ”1234567” 变成 “1,234,567”
'1234567'.replace(reg, ',')
思路是匹配连续三个数字\d{3},在它们前边加逗号。有两个问题:
- 如何从后往前匹配
/(?=\d{3})/这样会替换成,1,2,3,4,567,原因是从前向后依次匹配到了123 234 345 456, 所以1 2 3 4 5前边都加上了逗号- 增加结尾匹配
/(?=\d{3}$)/,这样会在结尾替换出1234,567- 如果把
$写在括号外边:/(?=\d{3})$/则表示匹配连续三个数字之前的位置,但是这个位置之后就是结尾。这应该是一种无效匹配
- 如果把
- 再给
\d{3}整体加上量词/(?=(\d{3})+$)/,就能匹配多组数字了1,234,567
- 如何避免开头有逗号
- 前边的表达式替换
123456789会出现,123,456,789,需要避免匹配到开头^ -
(?!^)可以匹配非开头(还是没明白为啥…),个人觉得应该有一个类似匹配字符的 ^ 来表示取反,而不是用位置来表示“开头”。开头和结尾这两个位置的定义还是很奇妙。 /(?!^)(?=(\d{3})+$)/可以替换出123,456,789- ::位置匹配如何做到「非」?:: ::两个位置连用 代表对同一个位置的「且」?::看上去两个位置连用的确表示一起描述同一个位置
- 前边的表达式替换
扩展:要求匹配 123456 12345678
/\B(?=(\d{3})+\b)/g
学习正则匹配有点像是学自然语言而不是编程语言——先学会实践怎么用,而不是学习如何实现(语法结构)
括号的作用
分组
要匹配连续的字符a可以写 /a+/,如果想匹配连续的ab,需要括号/(ab)+/,括号提供分组功能
分支结构
用 | 表示分支结构时,可选项的范围需要由括号包围。/(JS|Java) is the best lang/ 匹配 JS is the best lang 或者 Java is the best lang
如果没有括号 /JS|Java is the best lang/ 匹配 JS 或者 Java is the best lang,整个表达式都是分支
引用分组——JS实现
括号可以进行数据提取,必须配合使用实现环境的 API。下边以 JS 为例
简单来说就是可以将表达式中的匹配项以 JS 变量的方式获取到。
RegExp.prototype.exec、String.prototype.match、String.prototype.replace 三个函数都会得到一个数组(前两个是返回值,后一个会传给 lambda 的入参)
1 | /(\d{4})-(\d{2})-(\d{2})/.exec('2021-04-29') |
其中1~3个元素就是括号提取出的数据,第0个表示正则表达式匹配到的字符串。
另外提一句,返回值是一个添加了自定义属性的数组。Array 是 Object 的实例,自带
length属性,也可以像这里的返回值一样增加index、input、groups属性。
前边的例子中,调用 replace 之后也可以通过 RegExp.$1 RegExp.$2 RegExp.$3 拿到对应三个数据,不过这种方式已经废弃。这种实例能改变对象本身的变量,设计也很奇怪,不再深究。
反向引用
指表达式前边声明过的模式,后边有一份一模一样的引用
1 | // 匹配 2021-04-29 2021/04/29 2021.04.29 |
其中的 \1 就表示第一个分组(-|\/|\.),不论第一个分组匹配到什么,\1 的内容都一模一样。注意这里和复制粘贴一份 (-|\/|\.) 到后边有本质区别,反向引用使得两个模式有了关联。::为啥这里写([-/.])不行?::
随着分组数量增加,\1 \2 \3 后边的数字也增加
- 括号嵌套时,左括号的次序代表\后边的数字
1
2/^((\d)(\d(\d)))\1\2\3\4$/.exec("4564564566")
// [4564564566, 456(\1), 4(\2), 56(\3), 6(\4) ...] \10表示的是第10个分组,而不是\1和0- 引用不存在的分组时,
\1保持字面意思,代表一个字符
再另外提一句,在 JS 的字符串中,反斜杠
\有转义的作用
- 转义字符:
\0 \b \f \n \r \t \v \' \" \\\HHH后边跟三个000到377的八进制数代表一个字符,HHH代表对应 Unicode 码点(code point),一共能输出256种字符。\1 === \001遵循这个规则\xHH后边跟00到FF的十六进制数,同样代表对应 Unicode 码点,能输出256种字符\uXXXX后边也跟0000到FFFF的十六进制数,XXXX代表对应码点- 如果反斜杠后边不是特殊字符,则反斜杠会被省略
'\a' === 'a'
1 | '\172' === 'z' // true |
非捕获分组
可以用(?:p)来避免分组引用,括号内的匹配不会被提取,而只是作为分组或者分支结构用 /(?:a|b)(\d+)/ 不会引用a或b,只会引用到后边的数字