正则表达式是匹配模式,要么匹配字符,要么匹配位置。
基本语法
字符 | 含义 |
---|---|
\ | 转义 |
匹配位置: | |
匹配输入的开始 | |
$ | 匹配输入的结束 |
\b | 匹配单词的开始或结束(所谓单词的开始和结束,就是\b前后不同时是\w) |
x(?=y) | 正向前瞻,仅匹配后面紧跟着y的x |
x(?!y) | 负向前瞻,仅匹配后面不跟着y的x |
匹配字符 | |
\w | 字母、数字、下划线 |
\d | 数字 |
\s | 空白字符,包括空格、制表符、换页符和换行符 |
[xyz] | 匹配[]中的任意字符 |
x|y | 匹配x或y |
. | 默认匹配除换行符之外的任何单个字符 |
量词 | |
* | 匹配前一个表达式0次或多次 |
? | 匹配前一个表达式0次或1次 正则默认是贪婪匹配,也就是匹配尽量多的字符 在量词后的?表示当前匹配是惰性匹配 |
+ | 匹配前一个表达式1次或多次 |
{n} | 匹配前一个表达式n次 |
{n,} | 前一个表达式至少出现n次 |
flags | |
g | 全局匹配(global) |
i | 不区分大小写(ignoreCase) |
m | 多行搜索 |
基本方法
创建一个正则表达式
var reg = /\w+/; // 字面量
// 等价于
var reg = new RegExp('\\w+'); // 构造函数的字符串参数
注意:当使用构造函数创造正则对象时,需要常规的字符转义规则(也就是在转义字符前也要加反斜杠 \
)。
正则表达式(RegExp)的方法
-
test
/a/.test('a'); // true /a/.test('b'); // false
-
exec
regExp是一个有状态的对象,多次对同一个或者相等的字符串执行同样的方法,可以遍历所有匹配的结果。
相比test会返回更多内容,如果匹配成功会返回一个数组,并且更新lastIndex属性。
返回的数组其实是类数组,第一项是匹配的字符串,接着是捕获的分组,还有groups、index、input属性。
var reg = /(a|d)/g; var target = 'abcd'; var arr; while(arr = reg.exec(target)) { console.log(arr); } console.log(arr); // 长度为2的数组 // ["a", "a", index: 0, input: "abcd", groups: undefined] // ["d", "d", index: 3, input: "abcd", groups: undefined] // null
String对象的方法
-
split
var values = '1,2,3,4,5'; //以中文或英文逗号分隔的值 var arr = values.split(/,|,/); // ['1', '2', '3', '4', '5']
-
match
-
如果正则表达式有flag g,那么match方法会返回所有匹配的结果,但不会返回捕获组。
-
如果没有,就和exec方法很像,会返回第一个匹配和相关的捕获组,以及index、input、groups属性。如果不匹配,则返回null。
const str = 'Hello World'; console.log(str.match(/[A-Z]/)); // ["H", index: 0, input: "Hello World", groups: undefined] console.log(str.match(/[A-Z]/g)); // ["H", "W"]
-
-
replace
console.log('abcd'.replace(/ab/, 'AB')); // ABcd
-
search
返回匹配到位置的索引,匹配失败时返回-1。
console.log('hello World'.search(/[A-Z]/)); // 6
分组与捕获
引用
正则表达式中,括号的作用除了使结构更加清晰,还有一个更重要的功能:分组和捕获,也就是可以提取匹配到的数据或进行替换,而这个功能需要配合相关的api使用,比如:exec、match。
假设我们要匹配一个年月日,可以用这样的正则表达式匹配:
var reg = /\d{4}-\d{2}-\d{2}/;
reg.exec('2021-06-01'); // ["2021-06-01", index: 0, input: "2021-06-01", groups: undefined]
虽然匹配了,但要是我们想分别提取到年、月、日的信息呢?考虑下面的正则表达式:
var reg = /(\d{4})-(\d{2})-(\d{2})/;
reg.exec('2021-06-01'); // ["2021-06-01", "2021", "06", "01", index: 0, input: "2021-06-01", groups: undefined]
可以看到,返回的数组多了几项,从第二项开始,分别对应的是三个括号内匹配到的内容,这就是分组,在exec方法中可以捕获到,通过RegExp.9,也可以获取到对应的内容。
console.log(RegExp.$1); // 2021console.log(RegExp.$2); // 06console.log(RegExp.$3); // 01
反向引用
除了上面这种引用,还有一种反向引用,也就是正则本身引用之前出现的分组。
考虑这样的场景:我们要匹配三种格式的日期:yyyy-mm-dd、yyyy/mm/dd、yyyy.mm.dd。或许我们可以这样写正则:
var reg = /\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/;
虽然确实可以匹配上面三种格式的日期,但是这个正则表达式同时也匹配了像‘2021-06/02’这样的日期,要怎么样保持连接符号的一致呢?这就需要反向引用了。
var reg = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
这里的\1
,引用的就是前面出现的第一个分组,通过\1
、\2
、\3
这样的转义字符,可以捕获到前面的分组,无论前面匹配到什么,反向引用匹配的都是同样的具体某个字符。
如果遇到嵌套的括号,则以左括号为准,标识分组顺序。
非捕获分组
还是上面的例子,我们用括号包裹连接符,创建了分组,但捕获的时候,我们其实并不关心连接符号,而只关心匹配到的年月日,那么我们可以使用非捕获分组(?:)
:
var reg = /(\d{4})(?:-|\.|\/)(\d{2})(?:-|\.|\/)(\d{2})/;console.log(reg.exec('2021-06-01')); // ["2021-06-01", "2021", "06", "01", index: 0, input: "2021-06-01", groups: undefined]
如此,我们捕获到的分组,就只是年、月、日了。
正则的构建
构建正则之前,我们要考虑几个问题:
-
是否能使用正则
-
是否有必要使用正则,能用字符串API解决的简单问题,就没有必要使用正则。
-
是否有必要构建一个复杂的正则
对于密码匹配,假设规则有很多,那么构建出来的可能是一个很庞大的正则表达式,但其实也可以使用很多个小正则来做
function checkPassword(string) { if (!regex1.test(string)) return false; if (!regex2.test(string)) return false; if (!regex3.test(string)) return false; ...}
正则采用的是回溯的方法来匹配,对于太复杂的正则表达式,可能会影响到性能。
构建正则表达式时,还要注意:
- 匹配预期的字符串
- 不匹配非预期的字符串
- 效率优化:
- 使用具体型字符组代替通配符
- 使用非捕获型分组
- 独立出确定字符
- 提取分支的公共部分
- 减少分支的数量
使用场景
表单验证/提取
验证输入的某个值是否符合一个模式,使用match方法,如果匹配失败,返回null,匹配成功,则返回带有捕获分组信息的类数组。
切分
从逗号分隔的字符串中提取数据,兼容中文逗号和英文逗号。
const arr = value.split(/,|,/);