版权声明
转载请告知并注明来源作者
作者
:唐金健
网络昵称
:御焱
专栏
:优雅的前端
一、正则表达式简介
1、什么是正则表达式
正则表达式,又称规则表达式。(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。
简单的说,就是按照某种规则去匹配符合条件的字符串。
2、可视化正则表达式工具
Regexper:
二、RegExp对象
实例化
RegExp
的两种方式。
两种方式定义RegExp对象。
1、字面量
let reg = /[a-z]{3}/gmi;let reg = /[a-z]{3}/g;let reg = /[a-z]{3}/m;let reg = /[a-z]{3}/i;复制代码
标志
g
global 代表全局搜索。如果不添加,搜索到第一个匹配停止。m
Multi-Line 代表多行搜索。i
ignore case 代表大小写不敏感,默认大小写敏感。
2、构造函数
let reg = new RegExp('\\bis\\b', 'g');复制代码
因为JavaScript字符串中\
属于特殊字符,需要转义。
三、元字符
把元字符当作转义字符。
正则表达式有两种基本字符类型组成。
- 原义文本字符
- 元字符
1、原义文本字符
表示原本意义上是什么字符,就是什么字符。
2、元字符
是在正则表达式中有特殊含义的非字母字符。
* + ? $ ^ . | \ ( ) { } [ ]
字符 | 含义 |
---|---|
\t | 水平制表符 |
\v | 垂直制表符 |
\n | 换行符 |
\r | 回车符 |
\0 | 空字符 |
\f | 换页符 |
\cX | 控制字符,与X对应的控制字符(Ctrl + X) |
类似于转义字符。
四、字符类
表示符合某种特性的字符类别。
使用元字符[]
可以构建一个简单的类。
例子
表达式[abc]
把字符a
或b
或c
归为一类,表达式可以匹配这一类中的任意一个字符。
// replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。'a1b2c3d4e5'.replace(/[abc]/g, '0'); //010203d4e5复制代码
字符类取反
我们想要替换不是abc
中任意一个字符的字符。
// 元字符 ^ 创建一个 反向类/负向类'abcdefg'.replace(/[^abc]/g, '0'); //abc0000复制代码
五、范围类
匹配这一个范围内的字符。
如果我们想要匹配数字0-9
,那么我们可能会这样写[0123456789]
。
26
个字母,那么我们可能会这样写[abcdefghijklmnopqrstuvwxyz]
。 这样略显麻烦,所以才会有范围类。 例子
// 替换所有数字'a1c2d3e4f5'.replace(/[0-9]/g, 'x'); //axcxdxexfx// 替换所有小写字母'a1c2d3e4f5'.replace(/[a-z]/g, 'x'); //x1x2x3x4x5// []组成的类内部是可以连写的。替换所有大小写字母'a1C2d3E4f5G6'.replace(/[a-zA-Z]/g, '*'); //*1*2*3*4*5*6复制代码
疑问
如果我想替换数字,并且连带-
符号也一起替换呢?
// 替换所有数字和横杠'2018-5-21'.replace(/[0-9-]/g, '*'); //*********复制代码
六、预定义类
一些已经定义的类,可以直接使用。
字符 | 等价类 | 含义 |
---|---|---|
. | [^\r\n] | 除了回车、换行之外的所有字符 |
\d | [0-9] | 数字字符 |
\D | [^0-9] | 非数字字符 |
\s | [\t\n\x0B\r] | 空白符 |
\S | [^\t\n\x0B\r] | 非空白符 |
\w | [a-zA-Z_0-9] | 单词字符(字母、数字、下划线) |
\W | [^a-zA-Z_0-9] | 非单词字符 |
例子
替换一个 ab
+ 数字
+ 任意字符
的字符串
// 写法1'ab0c'.replace(/ab[0-9][^\r\n]/g, 'TangJinJian'); //TangJianJian// 写法2'ab0c'.replace(/ab\d./g, 'TangJinJian'); //TangJianJian复制代码
七、单词边界
字符 | 含义 |
---|---|
^ | 以xxx开始(不在中括号内时的含义) |
$ | 以xxx结束 |
\b | 单词边界 |
\B | 非单词边界 |
例子
我想替换的字符串,属于那种只在开头出现的。
'YuYan is a boy, YuYan'.replace(/^YuYan/g, 'TangJinJian'); //TangJinJian is a boy, YuYan复制代码
我想替换的字符串,属于那种只在结尾出现的。
'YuYan is a boy, YuYan'.replace(/YuYan$/g, 'TangJinJian'); //YuYan is a boy, TangJinJian复制代码
单词边界例子。
// 替换所有is为0'This is a man'.replace(/is/g, '0'); //Th0 0 a man// 替换所有is前面带有单词边界的字符串'This is a man'.replace(/\bis/g, '0'); //This 0 a man// 替换所有is前面没有单词边界的字符串'This is a man'.replace(/\Bis\b/g, '0'); //Th0 is a man复制代码
八、量词
用来处理连续出现的字符串。
字符 | 含义 |
---|---|
? | 出现零次或一次(最多出现一次) |
+ | 出现一次或多次(至少出现一次) |
* | 出现零次或多次(任意次) |
{n} | 出现n次 |
{n,m} | 出现n到m次 |
{n,} | 至少出现n次 |
我想替换字符串中连续出现10
次的数字为*
。
'1234567890abcd'.replace(/\d{10}/, '*'); //*abcd复制代码
我想替换字符串中的QQ号码。
'我的QQ是:10000'.replace(/[1-9][0-9]{4,}/, '19216811'); //我的QQ是:19216811复制代码
九、贪婪模式
尽可能多的匹配。
有这样的一种场景下的正则表达式,/\d{3,6}/
该替换3个数字还是6个数字呢,4、5个数字?
// 贪婪模式会尽可能的往多的方面去匹配'123456789'.replace(/\d{3,6}/, 'x'); //x789'123456789'.replace(/\d+/, 'x'); //x'123456789'.replace(/\d{3,}/, 'x'); //x复制代码
十、非贪婪模式
尽可能少的匹配。
如果我们想要最低限度的替换呢?
// 非贪婪模式使用 ? 尽可能的往少的方面去匹配'12345678'.replace(/\d{3,6}?/g, 'x'); //xx78'123456789'.replace(/\d{3,6}?/g, 'x'); //xxx复制代码
因为有g
标志,会匹配这段字符串里所有符合规则的字符串。
/\d{3,6}?/g
,12345678
中有两个符合条件的字符串,是123
和456
。所以替换结果是xx78
。 第二个规则/\d{3,6}?/g
,123456789
中有三个符合条件的字符串,是123
、456
和789
。所以替换结果是xxx
。 十一、分组
括号里的一些规则,分为一组。
我想替换连续出现3次的字母
和数字
。
//没有分组的情况下,后面的量词,只是表示匹配3次数字。'a1b2d3c4'.replace(/[a-z]\d{3}/g, '*'); //a1b2d3c4//有分组的情况下,分组后面的量词,表示符合这个分组里规则的字符串,匹配3次。'a1b2d3c4'.replace(/([a-z]\d){3}/g, '*'); //*c4复制代码
1、或
分组里有两种规则,只要满足其中一种即可匹配。
//我想把ijaxxy和ijcdxy都替换成*'ijabxyijcdxy'.replace(/ij(ab|cd)xy/g, '*'); //**复制代码
2、反向引用
可以把分组视为变量,来引用。
//我想把改变年月日之间的分隔符'2018-5-22'.replace(/(\d{4})-(\d{1,2})-(\d{1,2})/g, '$1/$2/$3'); //2018/5/22//我想替换日期,并且更改顺序'2018-5-22'.replace(/(\d{4})-(\d{1,2})-(\d{1,2})/g, '$2/$3/$1'); //5/22/2018复制代码
3、忽略分组
忽略掉分组,不捕获分组,只需要在分组内加上?:
// 忽略掉匹配年的分组后,匹配月的分组变成了$1,日的分组变成了$2'2018-5-22'.replace(/(?:\d{4})-(\d{1,2})-(\d{1,2})/g, '$1/$2/$3'); //5/22/$3复制代码
十二、前瞻
正则表达式从文本头部向尾部开始解析,文本尾部方向,称为“前”。
前瞻就是在正在表达式匹配到规则的时候,向前检查是否符合断言,后顾/后瞻方向相反。 JavaScript不支持后顾。 符合和不符合特定断言称为肯定/正向
匹配和否定/负向
匹配。
名称 | 正则 | 含义 |
---|---|---|
正向前瞻 | exp(?=assert) | |
负向前瞻 | exp(?!assert) | |
正向后顾 | exp(?<=assert) | JavaScript不支持 |
负向后顾 | exp(?<!assert) | JavaScript不支持 |
例子
有这样一个单词字符
+数字
格式的字符串,只要满足这种格式,就把其中的单词字符替换掉。
'a1b2ccdde3'.replace(/\w(?=\d)/g, '*'); //*1*2ccdd*3复制代码
有这样一个单词字符
+非数字
格式的字符串,只要满足这种格式,就把前面的单词字符替换掉。
'a1b2ccdde3'.replace(/\w(?!\d)/g, '*'); //a*b*****e*复制代码
十三、RegExp对象属性
global
是否全文搜索,默认false
。
ignore case
是否大小写敏感,默认是false
。multiline
多行搜索,默认值是false
。lastIndex
是当前表达式匹配内容的最后一个字符的下一个位置。source
正则表达式的文本字符串。 let reg1 = /\w/;let reg2 = /\w/gim;reg1.global; //falsereg1.ignoreCase; //falsereg1.multiline; //falsereg2.global; //truereg2.ignoreCase; //truereg2.multiline; //true复制代码
十四、RegExp对象方法
1、RegExp.prototype.test()
用来查看正则表达式与指定的字符串是否匹配。返回
true
或false
。
let reg1 = /\w/;reg1.test('a'); //truereg1.test('*'); //false复制代码
加上g
标志之后,会有些区别。
let reg1 = /\w/g;// 第一遍reg1.test('ab'); //true// 第二遍reg1.test('ab'); //true// 第三遍reg1.test('ab'); //false// 第四遍reg1.test('ab'); //true// 第五遍reg1.test('ab'); //true// 第六遍reg1.test('ab'); //false复制代码
实际上这是因为RegExp.lastIndex
。每次匹配到之后,lasgIndex
会改变。
lastIndex
是正则表达式的一个可读可写的整型属性,用来指定下一次匹配的起始索引。 let reg = /\w/g;// 每次匹配到,就会把lastIndex指向匹配到的字符串后一个字符的索引。while(reg.test('ab')) { console.log(reg.lastIndex);}// 1// 2复制代码
reg.lastIndex
初始时为0
,第一个次匹配到a
的时候,reg.lastIndex
为1
。第二次匹配到b
的时候,reg.lastIndex
为2
。
let reg = /\w\w/g;while(reg.test('ab12cd')) { console.log(reg.lastIndex);}// 2// 4// 6复制代码
reg.lastIndex
初始时为0
,第一个次匹配到ab
的时候,reg.lastIndex
为2
。第二次匹配到12
的时候,reg.lastIndex
为4
。第三次匹配到cd
的时候,reg.lastIndex
为6
。
let reg = /\w/g;// 匹配不到符合正则的字符串之后,lastIndex会变为0。while(reg.test('ab')) { console.log(reg.lastIndex);}console.log(reg.lastIndex);reg.test('ab');console.log(reg.lastIndex);// 1// 2// 0// 1复制代码
所以,这就是为什么reg.test('ab')
再多次执行之后,返回值为false
的原因了。
let reg = /\w/g;reg.lastIndex = 2;reg.test('ab'); //false复制代码
每次匹配的起始位置,是以lastIndex
为起始位置的。上述例子,一开始从位置2
开始匹配,位置2
后面没有符合正则的字符串,所以为false
。
2、RegExp.prototype.exec()
在一个指定字符串中执行一个搜索匹配。返回一个搜索的结果数组或
null
。
非全局情况
let reg = /\d(\w)\d/;let ts = '*1a2b3c';let ret = reg.exec(ts); //ret是结果数组// reg.lastIndex肯定是0,因为没有g标志。 没有g标志的情况下,lastIndex被忽略。console.log(reg.lastIndex + '\t' + ret.index + '\t' + ret.toString());console.log(ret);// 0 1 1a2,a// ["1a2", "a"]复制代码
返回数组是有以下元素组成的:
- 第一个元素是与正则表达式相匹配的文本。
- 第二个元素是
reg
对象的第一个子表达式相匹配的文本(如果有的话)。 - 第二个元素是
reg
对象的第二个子表达式相匹配的文本(如果有的话),以此类推。
// 子表达式就是分组。let reg = /\d(\w)(\w)(\w)\d/;let ts = '*1a2b3c';let ret = reg.exec(ts);console.log(reg.lastIndex + '\t' + ret.index + '\t' + ret.toString());console.log(ret); //输出结果数组// 0 1 1a2b3,a,2,b// ["1a2b3", "a", "2", "b"]复制代码
全局情况
let reg = /\d(\w)(\w)(\w)\d/g;let ts = '*1abc25def3g';while(ret = reg.exec(ts)) { console.log(reg.lastIndex + '\t' + ret.index + '\t' + ret.toString());}// 6 1 1abc2,a,b,c// 11 6 5def3,d,e,f复制代码
第一次匹配的是1abc2
,1abc2
的后一个字符的起始位置是6
,所以reg.lastIndex
是6
。
1abc2
的第一个字符的起始位置是1
,所以ret.index
是1
。 第二次匹配的是5def3
,5def3
的后一个字符的起始位置是11
,所以reg.lastIndex
是11
。
5def3
的第一个字符的起始位置是6
,所以ret.index
是6
。 十五、字符串对象方法
1、String.prototype.search()
执行正则表达式和
方法返回第一个匹配项的String
对象之间的一个搜索匹配。index
,搜索不到返回-1
。 不执行全局匹配,忽略g
标志,并且总是从字符串的开始进行检索。
我想知道Jin
字符串的起始位置在哪里。
'TangJinJian'.search('Jin'); //4'TangJinJian'.search(/Jin/); //4复制代码
search
方法,既可以通过字符串,也可以通过正则描述字符串来搜索匹配。
2、String.prototype.match()
当一个字符串与一个正则表达式匹配时,
提供match()
方法检索匹配项。RegExp
对象参数是否具有g
标志,对结果影响很大。
非全局调用的情况
如果RegExp
没有g
标志,那么match
只能在字符串中,执行一次匹配。
null
。 否则将返回一个数组,其中存放了与它找到的匹配文本有关的信息。 let reg = /\d(\w)\d/;let ts = '*1a2b3c';let ret = ts.match(reg);console.log(ret.index + '\t' + reg.lastIndex);console.log(ret);// 1 0// ["1a2", "a"]复制代码
非全局情况下和RegExp.prototype.exec()
方法的效果是一样的。
全局调用的情况
我想找到所有数字
+单词
+数字
格式的字符串。
let reg = /\d(\w)\d/g;let ts = '*1a2b3c4e';let ret = ts.match(reg);console.log(ret.index + '\t' + reg.lastIndex);console.log(ret);// undefined 0// ["1a2", "3c4"]复制代码
全局情况下和RegExp.prototype.exec()
方法的区别。在于,没有了分组信息。
String.prototype.match()
方法,效率要高一些。而且不需要写循环来逐个所有的匹配项获取。 3、String.prototype.split()
使用指定的分隔符字符串将一个
String
对象分割成字符串数组。
'a,b,c,d'.split(/,/); //["a", "b", "c", "d"]'a1b2c3d'.split(/\d/); //["a", "b", "c", "d"]'a1b-c|d'.split(/[\d-|]/); //["a", "b", "c", "d"]复制代码
4、String.prototype.replace()
返回一个由替换值替换一些或所有匹配的模式后的新字符串。模式可以是一个字符串或者一个正则表达式, 替换值可以是一个字符串或者一个每次匹配都要调用的函数。
常规用法
'TangJinJian'.replace('Tang', ''); //JinJian'TangJinJian'.replace(/Ji/g, '*'); //Tang*n*an复制代码
以上两种用法,是最常用的,但是还不能精细化控制。
精细化用法
我想要把a1b2c3d4
中的数字都加一,变成a2b3c4d5
。
'a1b2c3d4'.replace(/\d/g, function(match, index, orgin) { console.log(index); return parseInt(match) + 1;});// 1// 3// 5// 7// a2b3c4d5复制代码
回调函数有以下参数:
match
第一个参数。匹配到的字符串。group
第二个参数。分组,如果有n个分组,则以此类推n个group
参数,下面两个参数将变为第2+n
和3+n
个参数。没有分组,则没有该参数。index
第三个参数。匹配到的字符串第一个字符索引位置。orgin
第四个参数。源字符串。
我想把两个数字之间的字母去掉。
'a1b2c3d4e5f6'.replace(/(\d)(\w)(\d)/g, function(match, group1, group2, group3, index, orgin) { console.log(match); return group1 + group3;});// 1b2// 3d4// 5f6// a12c34e56复制代码