前言
最近比较详细地看了正则表达式相关的内容,虽然通常很多正则可以在网上直接找到答案,但为了应对工作中不同的需求,还是需要对正则有整体的认知,这样才能更好地使用正则解决问题。下面,就对我学到的东西做一些总结梳理,主要通过一些示例来说明问题。
正则表达式的作用
正则表达式主要功能是对字符串做查找、替换、验证、分割。
正则创建方式
正则创建方式有两种,字面量和构造函数方式。
字面量
字面量创建方式指的是变量直接创建正则,如:
let reg = /hello/;
需要注意的是,这里的hello是字符串,无法传入变量。可以通过一个例子来测试这一点。
let hello = '1234';
let reg = /hello/;
reg.test('1234') // 结果为false,hello并没有作为变量使用
reg.test('hello') // 结果为true, hello作为字符串使用
构造函数
构造函数方式指的是通过new一个正则对象的方式创建,如:
let reg = new RegExp('hello')
与字面量创建的方式不同,构造函数方式可以传入变量。例如:
let str = 'hello';
let reg = new RegExp(str);
let myStr = 'hello world';
reg.test(myStr); // 结果为true,str作为变量使用,相当于new RegExp(/hello/)
常用方法
常用方法有test、exec、match、split、replace、search。其中test和exec是正则方法;match、split、replace、search方法为字符串方法。
正则方法
test
返回boolean值,匹配到返回true,未匹配返回false。
exec
在字符串执行查找匹配的方法,返回数组(数组中只包含一个匹配结果)。
let reg = /\d+/g;
let str = 'abc123def456';
let execResult = reg.exec(str); // ['123', index: 3, input: 'abc123def456', groups: undefined]
reg.exec(str); // ['456', index: 9, input: 'abc123def456', groups: undefined]
即使是全局匹配,exec一次也只会返回一个匹配结果的详情,多次执行会依此向后查找结果。结果为数组,其中execResult['0']表示的是匹配到的字符串,execResult['index']表示字符串开始的索引execResult['input']表示执行匹配的字符串,execResult['groups']表示分组。
字符串方法
match
全局匹配只会返回匹配结果的数组,非全局会返回单个匹配的详细信息(包含分组、索引等信息)。
let reg = /\d+/g;
let reg1 = /\d+/;
let str = 'abc123def456';
str.match(reg); // ['123','456']
str.match(reg1); // ['123', index: 3, input: 'abc123def456', groups: undefined]
split
split方法用来拆分字符串,通常我们传入一个字符进行拆分,例如:
'a,b,c'.split(','); // 结果['a','b','c']
'abcdef'.split(''); // 结果['a','b','c','d','e','f']
也可以传入正则表达式来进行拆分,上述拆分也可写做:
'a,b,c'.split(/,/g); // 结果['a','b','c']
'abcdef'.split(/\B/g); // 结果['a','b','c','d','e','f']
replace
replace方法用来替换字符串中的指定内容,例如:
let str = 'a1b2c3d4e5f677';
let reg = /\d/g; // 匹配一个数字
str.replace(reg, '*'); // 结果: a*b*c*d*e*f***,将所有的数字替换成了*
其中replace方法的第二个参数也可传入函数,此函数可包含多个参数,依次为:
- 匹配到的字符串
- 匹配到的字符串的索引值
- 原来的字符串
- 分组
let str = '计算机科学与技术学院软件工程专业';
let reg = /软件/g;
let reg1 = /软件|计算机/g;
str.replace(reg, '*'); // 结果:计算机科学与技术学院*工程专业
str.replace(reg, function(arg) {
return '*'.repeat(arg.length);
}); // 结果:***科学与技术学院**工程专业
search
返回第一个匹配到的索引,类似indexOf
let str = 'abc123def456';
let reg = /\d/g;
str.search(reg); // 结果:3
正则表达式中的特殊字符
下面列出部分特殊字符,完整内容可参考正则表达式文档
| 字符 | 含义 |
|---|---|
| . | 匹配除了\n \r \u2028 \u2029以外的任意字符 |
| + | 匹配一次或多次指定字符 |
| * | 匹配0次或多次指定字符 |
| ? | 匹配0次或1次 |
| 以指定字符开始 | |
| $ | 以指定字符结束 |
| \w | 数字、字母、下划线 |
| \d | 数字 |
| [] | 字符集合 |
| {} | 数量 |
| + | 匹配一个或多个指定字符 |
| () | 分组 |
| (?=) | 先行断言/正向肯定断言 |
| (?!) | 正向否定断言 |
| (?<=) | 后行断言/负向肯定断言 |
| (?<!) | 负向否定断言 |
上述特殊字符的使用,通过示例可以更好地理解其含义。
. + * ?
let str = '<div>aaaaaaaa</div>';
let reg = /<div>.*<\/div>/g;
reg.test(str); // 结果:true,.*表示匹配任意字符0到多次,换成a*也同样满足
let reg1 = /<div>a+<\/div>/g;
reg1.test(str); // 结果:true,a+表示匹配a字符1次或多次
let reg2 = /<div>a?<\/div>/g;
reg2.test(str); // 结果:false,a?表示匹配a字符0次或1次,由于存在超过1次的a字符,因此不满足
^ $ \w
let str = 'abcdef';
let reg = /^a\w+f$/g;
reg.test(str); // 结果:true,以a开始,f结束,中间包含1到多个数字、字母或者下划线的组合
[]
字符集可以指定多个字符的集合,也可指定字符范围,还可以通过^符号排除指定字符集合或者范围。
let str = 'abc123def456';
str.replace(/[13]/g, '*');// 结果:'abc*2*def456',将1和3替换成了*
str.replace(/[^13]/g, '*');// 结果:'****1*3******',将除了1和3以外的字符替换成了*
str.replace(/[0-9]/g, '*');// 结果:'abc***def***',将所有数字替换成了*,此处的[0-9]相当于\d,匹配所有数字
str.replace(/[^0-9]/g, '*');// 结果:'***123***456',将所有非数字字符替换成了*
str.replace(/[a-z]/g, '*');// 结果:'***123***456',将所有小写字母替换成了*
{}
{}用于匹配数量,可以利用{}实现+、*、?相同的效果。
let str = 'abcaabbccaaabbbcccaaaaaaaaaaa';
str.replace(/a{2}/g,'*'); // 结果:'abc*bbcc*abbbccc*****a',将两个连续的a替换成*
str.replace(/a{3}/g, '*');// 结果:'abcaabbcc*bbbccc***aa',将3个连续的a替换成*
str.replace(/a{2,4}/g,'*');// 结果:'abc*bbcc*bbbccc***',将2个到4个连续的a替换成*
str.replace(/a{2,}/g);// 结果:'abc*bbcc*bbbccc*',将2个或者多个连续的a替换成*
通过上述示例,可以总结出:
- {n}表示匹配n次
- {a,b}表示匹配a到b次
- {a,}表示匹配至少a次,无上限
因此,*也可以写作{0,},+也可以写作{1,},?也可以写作{0,1}。
()
()表示分组,把一部分正则规则匹配分成一个小组,方便后面对小组做处理。
let str = 'abcabc123abcabc456';
str.replace(/abcabc/g, '*'); // 结果:'*123*456'
// 等同于
str.replace(/(abc){2}/g, '*'); // abc作为一个分组,出现两次
还可以获取分组中匹配到的值
let str = 'abc123def456';
let reg = /abc\d+/g;
let reg1 = /(abc)(\d+)/g;
let reg2 = /(?:abc)(\d+)/g;
str.match(reg); // 结果:['abc123']
str.match(reg1); // 结果:['abc123']
// 使用分组后,可以通过RegExp.$1,RegExp.$2方式获取匹配到的分组值,RegExp会保存最近一次的匹配结果
RegExp.$1; // 'abc'
RegExp.$2; // '123'
str.match(reg2); // 结果:['abc123'],分组中添加?:表示不捕获分组,这样RegExp中只会保存(\d+)这个分组的结果
RegExp.$1; // '123'
'abc123abc456abc789'.match(reg1);// 结果:['abc123','abc456','abc789'] RegExp中只会保存最后一个匹配的分组结果
RegExp.$1; // 'abc'
RegExp.$2; // '789'
在使用replace替换指定内容时,也可以使用分组捕获匹配到的内容。
let str = '1994-12-18';
let reg = /(\d{4})-(\d{2})-(\d{2})/g;
str.replace(reg, '$3/$2/$1'); // 结果:18/12/1994
通过(?<name>)可以对分组进行命名。
let str = '1994-12-18';
let reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/g;
reg.exec(str); // 结果:['1994-12-18','1994','12','18',group: { year: '1994', month: '12', day: '18' }, index: 0,input: '1994-12-18']
断言
断言分为正向肯定(先行断言)、正向否定、负向肯定(后行断言)、负向否定四种。
(?=)正向肯定(先行断言)
当已知需要匹配的内容后面会跟着特定正则匹配规则时,使用先行断言获取匹配内容。如下方例子,获取span标签上的所有属性,此处我们知道,属性后面会有="出现一次,因此可以使用先行断言来匹配所有属性。
let str = '<span id="spanId1" title="标题标题"></span><span id="spanId2"></span><span id="spanId3"></span>';
let reg = /[^\s]+(?=(="){1})/g;
str.match(reg); // 结果:['id', 'title', 'class']
(?!)正向否定
正向否定与正向肯定相反,匹配内容后面不跟着特定正则规则,
let str = 'iphone6iphone7iphone13iphonenumber';
str.replace(/iphone(?=\d{1,2})/g, '苹果'); // 结果:'苹果6苹果7苹果13iphonenumber',将后面跟着数字的iphone替换成苹果
str.replace(/iphone(?!\d{1,2})/g, '苹果'); // 结果:'iphone6iphone7iphone13苹果number',结果与正向肯定相反,将后面没有跟着数字的iphone替换成苹果
(?<=)负向肯定(后行断言)
当已知需要匹配的内容前面会有特定正则匹配规则时,使用后行断言获取匹配内容。
如下方的例子,获取字符串中所有的id的值,我们知道,id值的前面会有id=",因此可以使用后行断言来匹配id的值。
// 获取字符串中所有的id值
let str = '<span id="spanId1" title="标题标题"></span><span id="spanId2"></span><span id="spanId3"></span>';
let reg = /(?<=id=")[^"]*/g;
str.match(reg); // 结果:[ "spanId1", "spanId2", "spanId3" ]
(?<!)负向否定
负向否定与负向肯定相反,匹配内容前面没有特定正则。
let str = '10px;12px;13px;npx';
str.replace(/(?<=\d+)px/g,'像素'); // 结果:'10像素;12像素;13像素;npx'
str.replace(/(?<!\d+)px/g, '像素'); // 结果:'10px;12px;13px;n像素',结果与负向肯定相反
匹配模式
正则表达式有多个可选参数,允许配置全局搜索或者不区分大小写等。
| 标志 | 描述 |
|---|---|
| g | 全局搜索 |
| i | 不区分大小写 |
| m | 多行搜索 |
| s | 允许.匹配换行符 |
| u | 使用unicode码的模式进行匹配 |
| y | 粘性搜索,匹配从目标字符串的当前位置开始 |
多行模式
let str = 'abc
def';
let reg = /^/g;
str.replace(reg, '*');
/**
结果:*abc
def
**/
let reg1 = /^/gm;
str.replace(reg1, '*');
/**
结果:*abc
*def
**/
粘性模式
仅与目标字符串中此正则表达式的lastIndex属性指示的索引匹配(并且不尝试与任何后续索引匹配),可以通过下方例子来理解这句话:
let str = 'abc12345'
let reg = /\d/gy;
console.log(reg.lastIndex); // 未修改时,lastIndex值为0
console.log(reg.exec(str)); // null,从索引0开始查找,没有不会尝试后续索引,因此匹配结果为空
console.log(reg.exec(str)); // null
// 修改lastIndex值
reg.lastIndex = 3;
console.log(reg.exec(str)); // ['1',index:3,input:'abc12345',group:undefined],修改了lastIndex后,会从索引3开始找,因此能够找到
console.log(reg.exec(str)); // ['2',index:4,input:'abc12345',group:undefined]
console.log(reg.lastIndex); // 3