引言:过去我就不怎么喜欢正则,总感觉它很难很枯燥,也时常羡慕别人写出来的很厉害的正则
如果你有这样的烦恼,那么这篇文章将会非常适合你,只需要花费少量的时间,你就能应对工作中绝大部分的正则问题!!!
正则表达式就是匹配模式,匹配字符或者位置
一、首先我们先来认识一下什么是正则?
正则表达式又叫做规则表达式(于我们而言,
正则表达式其实也是一个对象,而要使用,得先创建一个正则表达式对象),然后计算机就会按照我们创建的正则表达式去检查或提取字符串中的内容
二、创建正则对象:
1、使用RegExp构造函数 创建一个正则表达式
let reg = new RegExp('匹配规则','匹配模式')- 其中匹配模式是可选
i表示忽略大小写g全局匹配m多行搜索s允许.匹配换行符u使用Unicode码的模式进行匹配y执行"粘性(sticky)"搜索,匹配从目标字符串的当前位置开始
- 更加灵活,参数是字符串,所以可以使用
字符串拼接的方式使用变量
2、使用字面量形式来创建一个正则表达式
let reg = /匹配规则/匹配模式- 匹配模式(同上)
- 创建方式简单,但字面量形式无法包含变量
3、正则表达式的方法
1.regexp.exec(str) 方法在一个指定字符串中执行一个搜索匹配。返回一个结果数组或 null
- 参数:
str,要匹配正则表达式的字符串 - 返回值:
- 匹配失败返回
null;将正则表达式的lastIndex重置为0 - 匹配成功返回一个数组,更新
lastIndex属性- (第一项)完全匹配成功的文本
- (第二项开始)后续的每一项都对应一个匹配成功的捕获组,即一个
(x)
- 返回数组的额外属性:
index(所匹配到字符的开始位置的索引值)input(用于匹配的原始字符串)groups(命名捕获组对象,键是名称,值是捕获组),若没有定义命名捕获组,则groups的值为undefinedindices(可选)此属性仅在设置了d标志时存在,它是一个数组,其中的每一个元素表示一个子字符串的边界
- 匹配失败返回
const reg2 = /(abc)(cde)/g;
const str2 = 'aababccde';
let result2 = reg2.exec(str2);
console.log(result2);// ['abccde', 'abc', 'cde'];
console.log(result2.index);// 3
console.log(result2.input);// aababccde
console.log(result2.groups);// undefined
2.regexp.test(str) 方法执行一个检索,用来查看正则表达式与指定的字符串是否匹配。返回 true 或 false
- 参数:
str,要匹配正则表达式的字符串 - 返回值:匹配成功返回
true,反之false
let str = 'hello world!';
let result = /^hello/.test(str);
console.log(result);// true
3.regexp[Symbol.match](str) 方法用于获取匹配结果
- 参数:
str,要匹配正则表达式的字符串 - 返回值:
- 如果没有匹配到则返回
null - 如果使用
g匹配模式,则将返回与正则表达式完整匹配的所有结果,但不会返回捕获组。 - 如果未使用
g匹配模式,则仅返回第一个完整匹配及其相关的捕获组(Array),在这种情况下,返回的数组将具有如下所述的其他属性。groups: 一个命名捕获组对象,其键名是捕获组名称,值是对应捕获组,如果未定义命名捕获组,则为undefinedindex: 所匹配到的结果的开始位置索引input: 用于匹配的字符串。
- 如果没有匹配到则返回
regexp[Symbol.match](str)和str.match(regexp)都是在String.prototype.match()上调用的,所以这两个方法的返回值是一样的
// 未使用 g
let result1 = 'aababccde'.match(/(abc)|(cde)/);
let result2 = /(abc)|(cde)/[Symbol.match]('aababccde');
console.log(result1); // ['abc', 'abc', undefined]; 仅第一个匹配结果和相关捕获组
console.log(result2); // ['abc', 'abc', undefined];
console.log(result2.index); // 3
console.log(result2.input); // aababccde
console.log(result2.groups); // undefined
// 使用 g
let result3 = 'aababccde'.match(/(abc)|(cde)/g);
let result4 = /(abc)|(cde)/g[Symbol.match]('aababccde');
console.log(result3); // [ 'abc', 'cde' ] 所有匹配结果,没有捕获组
console.log(result4); // [ 'abc', 'cde' ]
4. regexp[Symbol.matchAll](str) 方法返回对字符串使用正则表达式的所有匹配项
- 参数:
str,要匹配正则表达式的字符串 - 返回值:一个
迭代器(可迭代的属性值是数组,第一项为完全匹配的字符,后续项为相关捕获组),类数组对象迭代器可以转换为数组 regexp[Symbol.matchAll](str)和str.matchAll(regexp)都是在String.prototype.matchAll()上调用的,所以这两个方法的返回值是一样的- !!!
str.matchAll(regexp)中的参数正则必须要使用g匹配模式,不然会报错
let reg = /[0-9]+/g;
let str = '2016-01-02';
let result1 = reg[Symbol.matchAll](str);
let result2 = str.matchAll(reg);// 这里的正则表达式一定要加上全局匹配模式 g ,不然会报错
// 将返回的迭代器中的每一项(也是数组)的第一项(完全匹配的字符)提取出来
console.log(Array.from(result1, (x) => x[0])); // ["2016", "01", "02"]
console.log(Array.from(result2, (x) => x[0])); // ["2016", "01", "02"]
5.regexp[Symbol.replace](str, newSubStr|function) 方法会在一个字符串中用给定的替换器,替换所有符合正则匹配规则的匹配项,并返回完成替换的新字符串。用来替换的参数可以是一个字符串或是一个针对每次匹配的回调函数。
- 参数:
str,正则要替换的目标字符串newSubStr,类型是字符串的替换器- 或者是
function,针对每次匹配结果而生成新的子字符串的回调函数替换器
- 返回值:用替换器替换相应匹配项后的新字符串
regexp[Symbol.replace](str, newSubStr|function)和str.replace(regexp)都是在String.prototype.replace()上调用的,所以这两个方法的返回值是一样的
let reg = /-/g;
let str = '2016-01-01';
let newstr1 = reg[Symbol.replace](str, '/');
let newstr2 = str.replace(reg, '/');
console.log(newstr1); // 2016/01/01
console.log(newstr2); // 2016/01/01
6.regexp[Symbol.search](str) 方法在给定字符串中的进行搜索以取得符合正则匹配规则的项。
- 参数:
str,搜索目标的字符串 - 返回值:返回匹配规则找到的第一个匹配项的
开始索引,没找到返回-1 regexp[Symbol.search](str)和str.search(regexp)都是在String.prototype.search()上调用的,所以这两个方法的返回值是一样的
let reg = /-/g;
let str = '2016-01-01';
let newstr1 = reg[Symbol.search](str);
let newstr2 = str.search(reg);
console.log(newstr1); // 4
console.log(newstr2); // 4
7.regexp[Symbol.split](str[, limit]) 方法使用指定的分隔符字符串将一个长的字符串分割成子字符串数组,以一个指定的分割字串数量的整数来决定每个拆分的位置。
- 参数:
str切割操作的目标字符串;limit(可选)一个为了限制切割数量的整数 - 返回值:包含子字符串的数组
regexp[Symbol.split](str[, limit])和str.split(regexp)都是在String.prototype.split()上调用的,所以这两个方法的返回值是一样的
let reg = /-/g;
let str = '2016-01-01';
let newstr1 = reg[Symbol.split](str);
let newstr2 = str.split(reg);
console.log(newstr1); // [ '2016', '01', '01' ]
console.log(newstr2); // [ '2016', '01', '01' ]
三、接下来我们来认识一下正则中有特殊含义的标点符号
这些符号有:
^ $ . * + - ? = ! : | \ / ( ) [ ] { }
1、|,表示 或
- 例如 a|b|c 即表示匹配含有a或者b或者c的字符串
2、[] ,表示字符集,内部使用()无效
- 内部的内容存在
或的关系,[abc]等价于a|b|c
3、-,表示连字符,从左到右,和[]组合使用:
[a-z]任意小写字母[A-Z]任意大写字母[A-z]任意字母(大写A,小写z)[0-9]任意数字- 其他区间
4、^,两个含义:字符串开始 和 非
- 直接使用:
^a,表示以a开始的字符串 - 在
[]中使用:[^ab]表示不仅仅只有a或者b,即不匹配字符串"a"和"b"
5、$,字符串的结尾
- 例如:
c$表示以c结束的字符串
6、.表示除换行符和其他Unicode行终止符外的任意字符
7、{},表示符号前面的单个字符或者()出现的次数
- 例如:
a{2,4},匹配aa、aaa和aaaa!!!跟在要循环的字符后面 {m,n},表示至少m次,至多n次{m,},表示至少m次{m},刚好m次
8、+,表示符号前面的单个字符至少出现1次,等价于{1,},例如a+
9、?,表示符号前面的单个字符出现0次或1次,等价于{0,1},例如a?
10、*,表示符号前面的单个字符至少出现0次(任意次),等价于{0,},例如a*
!!!注意,当
{}、?、*和+等量词的后面添加?就会变成惰性匹配,不再是至少多少次到至多多少次,就只会执行至少多少次,例如:a{1,5}?表示只匹配一个a,不再匹配aa、aaa、aaaa和aaaaa了
11、\,转义字符,用于转义具有特殊意义的字符
-
例如:在
字面量中标点符号+要表示它们原本的作用而非特殊含义时,就需要使用\+ -
其他的符号要表示原本含义,也要加转义的
\ -
某些字母加上转义字符\可以表示特殊含义 -
在字符串中,用作转义的
\的本身也需要被转义一次,所以在字符串中要表示+的原本含义,需要使用\\+ -
在字面量中表示一个
*要使用\*;在字符串中要表示一个*则需要用\\*来表示 -
!!!在
字面量中表示一个\要使用\\;在字符串中要表示一个\则需要用\\\\来表示
12、\加上单个字母表示特殊含义
字符
\w任意字母、数字、_ 等价于[A-z0-9_]\W除了字母、数字、_ 等价于[^A-z0-9_]\d任意数字 等价于[0-9]\D除了数字 等价于[^0-9]\s空格、制表符、换行符和换页符\S除了空格、制表符、换行符和换页符
位置
\b单词边界 匹配一个字符前面或者后面为单词边界- 单词边界:\w 与 \W 之间的位置,也包括 \w 与 ^ 之间的位置,和 \w 与 $ 之间的位置
- !!!\w:包含任意字母,0-9的数字和下划线字符
- 例如:字符串
how are you- 正则
/\ba/会匹配a,因为a前面是单词边界 - 正则
/\br/不会匹配r,因为r前面不是单词边界 - 正则
/w\b/会匹配w,因为w后面是单词边界 - 正则
/o\b/不会匹配o,因为o后面不是单词边界
- 正则
\B除了单词边界的其他位置
13、()括号中的元素会被当做一个整体,优先级高,再按相应规则去匹配
- 1.
(x)整体匹配并记住字符x,例如:/(foo)(bar)\1\2/在这个正则表达式中,第一个()中的foo将作为一个整体被匹配,且被记住,在被记住后可以使用\1来表示,同理第二个()中的bar也将作为一个整体被匹配,且被记住,在被记住后可以使用\2来表示,依此类推,如果存在,可以使用\3、\4或\5等- !!!是可以使用
\n来表示被记住的整体字符,而非必须要使用
- !!!是可以使用
- 2.
(?:x)整体匹配但不记住字符x,例如:/(?:foo){1,2}/,在这个正则中foo将会被当做一个整体被匹配1次或2次 - 3.
x(?=y),匹配那些后面有y的x,又被叫做先行断言- 例如:
/a(?=b)/,在字符串"abacada"中只会匹配第一个a,因为只有这个a后面有b,其他的a后面没有跟着b
- 例如:
- 4.
(?<=y)x,匹配那些前面有y的x,又被叫做后行断言- 例如:
/(?<=b)a/,在字符串"bacadaa"中只会匹配第一个a,因为只有这个a前面有b,其他的a前面都没有b
- 例如:
- 5.
x(?!y),匹配那些后面没有y的x,正向否定查找- 例如:
/a(?!b)/,在字符串"abacada"中不会匹配第一个a,因为这个a后面有b,其他的a都会被匹配,因为后面没有跟着b
- 例如:
- 6.
(?<!y)x,匹配那些前面没有y的x,又被叫做反向否定查找- 例如:
/(?<!b)a/,在字符串"bacadaa"中只不会匹配第一个a,因为这个a前面有b,其他的a都会被匹配,因为前面都没有b
- 例如:
总结:
对于正则来说,单个的符号及其含义的多样性就已经让很多程序员望而却步,但是正则的精确匹配没有多大的实用价值的,
符号和规则的组合带来的横向和纵向模糊匹配才能真正展示正则的强大。在使用的过程中慢慢的去了解它们的有什么用?什么时候用?最后的最后,强烈推荐小伙伴可以去看看老姚的正则表达式迷你小书