前言
最近的项目里有多处格式和查询使用到了正则表达。简单的正则语法会写了,但是一直没有花功夫研究遇到的复杂正则。刚好趁着这一波研究的兴趣,总结一下自己学习到的东西,不再没有灵魂的 google 后复制粘贴了。以后再看别人写的优雅代码,再也不用眼馋了。
推荐工具
regexper的解析真的非常有用处,它可以图形化的展示出一个正则的所有的逻辑分支。对于自己难以读懂的长正则,或者自己写的复杂正则,都可以先解析成图形化的逻辑来分析,大大降低了难度。如下图随便写的一个正则解析:
正则表达式基础知识
字符类
字符 | 匹配 | 字符 | 匹配 |
---|---|---|---|
[] | 方括号内的任何字符 | [^] | 不在方括号内的任何字符 |
\w | [a-zA-Z0-9] | \W | [^a-zA-Z0-9] |
\s | 任何Unicode空白字符 | \S | 任何Unicode空白字符 |
\d | [0-9] | \D | [^0-9] |
. | 除了换行符和其它Unicode终止符之外的任意字符 | \b | 单词的边界。\w和\W之间的边界。 |
重复
字符 | 含义 |
---|---|
{n,m} | n <= matches < m |
{n,} | matches >= n |
{n} | matches === n |
? | {0,1} |
+ | {1,} |
* | {0,} |
注意:
*
和 ?
可以匹配0个字符,因此它们允许什么都不匹配。
/a*/.test('bbbb') //=> true
复制代码
非贪婪重复
正常匹配重复字符是尽可能多的匹配,我们称之为“贪婪的”匹配。
非贪婪的匹配级尽可能的少匹配。只需在待匹配字符后加一个问号
// 贪婪匹配
'bbbb'.match(/b+/g) // => ["bbbb"]
// 非贪婪匹配
'bbbb'.match(/b+?/g) // => ["b", "b", "b", "b"]
复制代码
正则表达式匹配总是会寻找字符串中第一个可能匹配的位置。因此不会考虑它子串中更短的匹配。
'aaab'.match(/a+b/g) // => ["aaab"]
// 你可能想得到 ’ab‘, 但是并不会。
'aaab'.match(/a+?b/g) // => ["aaab"]
复制代码
选择
字符 | 含义 |
---|---|
| | 或关系 |
引用
字符 | 含义 |
---|---|
() | 1. 把单独的项合成子表达式,以便像处理一个独立单元那样使用。 |
2. 在完整模式中定义子模式。当一个正则表达式成功和目标字符串匹配时,可以从目标串中抽出子模式匹配的部分。 | |
3. 允许在同一个正则表达式的后面引用前面的子表达式。\1 引用的是第一个带圆括号的子表达式。 \3 引用的是第三个。因为可以嵌套,所以是按参与计数的左括号的位置来决定。 | |
\n | 和第 n 个分组匹配的字符串匹配 |
\k | 在语法模式中调用命名的分组,同\n类似 |
引用的一个好处就是并不是子表达式相同,而是与引用子表达式模式匹配的文本相等。即一个字符串中各个部分包含的是完全相同的字符串。
例如匹配单引号或双引号:
// 它并不要求左右 单双引号 是匹配的
const partten = /['"][^'"]*['"]/g
// 改为
const partten = /(['"])[^'"]*\1/g
复制代码
分组
字符 | 含义 |
---|---|
() | 可以记住和这个组合匹配的字符串以供以后的引用使用 |
(?:) | 单纯分组,不记住与该组合匹配的字符串 |
(? ) | 将分组以 name 命名 |
锚点
类似 \b不匹配任何字符,指定匹配发生的合法位置。有时我们成为正则表达式的锚。
字符 | 含义 |
---|---|
\b | 单词边界(a word boundary),\w和\W之间的边界,或位于一个单词与字符串开始和结束之间的边界。 |
\B | 非单词边界(Non-word boundary) |
^ | 字符串的开始 |
$ | 字符串的结束 |
前后关联约束
字符 | 含义 |
---|---|
(?=) | 前置约束-存在 |
(?!) | 前置约束-排除 |
(?<=) | 后置约束-存在 |
(?<!) | 后置约束-排除 |
修饰符
字符 | 含义 |
---|---|
i | 执行不区分大小写 |
g | 找到所有匹配,否则在找到第一个后就停止 |
m | 多行匹配,^匹配一行的开头,$匹配一行的结束 |
s (es2018) | 使. 匹配所有字符,包括换行符 |
JavaScript基础知识
用于模式匹配的 string 方法
String.prototype.search()
返回第一个满足条件的匹配结果在整个字符串中的位置。如果没有任何匹配,则返回-1。
注意: 不支持全局搜索。只返回第一个匹配的位置信息。
"This is a test text".search(/th/i) // => 0
复制代码
String.prototype.replace()
字符串对象的replace方法可以替换匹配的值。它接受两个参数,第一个是正则表达式,表示搜索模式,第二个是替换的内容。
var str = ' #id div.class ';
str.replace(/^\s+|\s+$/g, '')
// "#id div.class"
复制代码
replace方法的第二个参数可以使用美元符号$,用来指代所替换的内容。
符号 | 含义 |
---|---|
$& | 匹配的子字符串 |
$` | 匹配结果前面的文字 |
$’ | 匹配结果后面的文字 |
$n | 匹配成功的第n组内容,n从1开始。 |
$ | 匹配成功的命名组内容 |
$$ | 指代美元 $ |
replace方法的第二个参数还可以是一个函数,将每一个匹配内容替换为函数返回值。
var a = 'The quick brown fox jumped over the lazy dog.';
a.replace(pattern, function replacer(match) {
return match.toUpperCase();
});
复制代码
String.prototype.match()
返回匹配的数组或null。 g 修饰符有效。
String.prototype.split()
str.split(separator, [limit])
复制代码
RegExp对象
RegExp.prototype.test()
返回布尔类型
如果正则表达式带有g
修饰符,则每一次test
方法都从上一次结束的位置开始向后匹配。
带有g
修饰符时,可以通过正则对象的lastIndex
属性指定开始搜索的位置。
var r = /x/g;
var s = '_x_x';
r.lastIndex = 4;
r.test(s) // false
复制代码
如果正则模式是一个空字符串,则匹配所有字符串。
RegExp.prototype.exec()
正则实例对象的exec
方法,用来返回匹配结果。如果发现匹配,就返回一个数组,成员是匹配成功的子字符串,否则返回null
。
var s = '_x_x';
var r1 = /x/;
var r2 = /y/;
r1.exec(s) // ["x"]
r2.exec(s) // null
复制代码
如果正则表示式包含圆括号(即含有“组匹配”),则返回的数组会包括多个成员。第一个成员是整个匹配成功的结果,后面的成员就是圆括号对应的匹配成功的组。也就是说,第二个成员对应第一个括号,第三个成员对应第二个括号,以此类推。整个数组的length
属性等于组匹配的数量再加1。
var s = '_x_x';
var r = /_(x)/;
r.exec(s) // ["_x", "x"]
复制代码
exec
方法的返回数组还包含以下两个属性:
input
:整个原字符串。index
:整个模式匹配成功的开始位置(从0开始计数)。
如果正则表达式加上g
修饰符,则可以使用多次exec
方法,下一次搜索的位置从上一次匹配成功结束的位置开始。
ES2018 RegExp新特性
好吧,写完这篇文章后,发现了ES 2018 的新特性文章都出来啦。。。于是趁热,自己赶紧加上了。
1. 新增修饰符 s
点(.)是正则表达式模式中的特殊字符,它匹配除换行符之外的任何字符。 这就导致如果我们要匹配包括换行符在内的所有字符只能通过特殊方法实现。比如[\d\D]
...
ES2018 引入了一种模式,其中点可用于实现相同的结果。可以使用s标志在每个正则表达式的基础上激活此模式:
const regold = /test.test/
console.log(regold.test('test\ntest')) // => false
const reg = /test.test/s
console.log(reg.test('test\ntest')) // => true
复制代码
2. 可命名组
符号 | 含义 |
---|---|
(?<name> ) | 将分组以 name 命名 |
\k<name> | 在语法模式中调用命名的分组匹配的字符串,同\n类似 |
$<name> | 匹配成功的命名组内容 |
3. 支持后行断言
好吧,原来一直没支持啊。
4. Unicode 属性转义
字符 | 含义 | 示例 |
---|---|---|
\p | 匹配字符串中的 Unicode 字符 | /\p{Number}/u 匹配 Unicode 中的任何十进制数 /\p{Alphabetic}/u 匹配任意 Unicode 字母字符 |
\P | 否定模式 |
练习题
基础知识已经写的差不多了。记住使用说明的最好方式就是做做题啦。
- 与搜索字符串开始处的 3 个数字匹配。
- 与除 a、b 和 c 以外的任何字符匹配。
'1234567'.match(/\d{1,3}/g)
的结果。(贪婪匹配)- 不以“th”开头的单词匹配。
- 去除字符串首尾的空格。
- 三分位格式化一个数字。
- 对密码应用以下限制:其长度必须介于 4 到 8 个字符之间,并且必须至少包含一个数字。
- 获取url中的属性对应的值
还有一些正则练习的网站:
callumacrae.github.io/regex-tuesd…
如果大家知道有意思的正则题目,欢迎分享哇~