正则表达式使用总结

139 阅读6分钟

前言

最近比较详细地看了正则表达式相关的内容,虽然通常很多正则可以在网上直接找到答案,但为了应对工作中不同的需求,还是需要对正则有整体的认知,这样才能更好地使用正则解决问题。下面,就对我学到的东西做一些总结梳理,主要通过一些示例来说明问题。

正则表达式的作用

正则表达式主要功能是对字符串做查找、替换、验证、分割。

正则创建方式

正则创建方式有两种,字面量和构造函数方式。

字面量

字面量创建方式指的是变量直接创建正则,如:

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。

image.png

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方法的第二个参数也可传入函数,此函数可包含多个参数,依次为:

  1. 匹配到的字符串
  2. 匹配到的字符串的索引值
  3. 原来的字符串
  4. 分组
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