正则表达式最常用于处理和校验字符串,是非常好用的简便工具。如果用的熟练,能够极大的提高工作效率。并且在阅读前端框架源码的时候经常也会遇到正则,记录在此以备忘。
1 | let reg = /^\d{3}$/ |
/^\d{3}$/
表示匹配字符串是以三个数字开头和结尾的的字符串。^
表示开头的位置$
表示结束的位置\d
表示[0-9]的数字{3}
表示前面的符号出现的3次
惰性匹配和贪婪匹配
所谓惰性或者贪婪都是在全局搜索符g的修饰下才会有的。
在量词后面追加一个?,就可使贪婪变为懒惰
1 | var greedyReg = /\d{2,5}/g; |
正则实用实例
1 | // 匹配16进制颜色 |
一、字符组和量词
1. 两种模糊匹配
1 | // 横向模糊 |
2. 字符组
1 | // [123] 表示1、2、3中的一个 |
字符组 | 具体含义 |
---|---|
\d | [0-9] |
\D | [^0-9] |
\w | [0-9a-zA-Z_] |
\W | [^0-9a-zA-Z] |
\s | 空白符 space缩写 |
\S | 非空白符 |
. | 表示几乎所有字符 |
3. 量词
惰性量词 | 贪婪量词 |
---|---|
{m, n}? | {m, n} |
{m, }? | {m, } 等价于 {m,m} |
?? | ? 等价于 {0,1} |
+? | + 等价于 {1,} |
*? | * 等价于 {0,} |
惰性匹配,就是尽可能少的匹配
4. 多选分支
- (p1|p2|p3) ,其中 p1、p2 和 p3 是子模式,用 |(管道符)分隔,表示其中任何之一。
分支结构是惰性的,即当前面的匹配上了,后面的就不再尝试了。
例子汇总一:
1 | // 匹配 16 进制颜色值 #fff #fa541d |
二、位置匹配
在 ES5 中,共有 6 个锚:^、$、\b、\B、(?=p)、(?!p)
^ 匹配开头,在多行匹配中匹配行开头
$(美元符号)匹配结尾,在多行匹配中匹配行结尾。
\b 是单词边界,具体就是 \w 与 \W 之间的位置,也包括 \w 与 ^ 之间的位置,和 \w 与 $ 之间的位置。
(?=p) p前面的位置
(?!p) 不是p前面的位置
(?<=p) p后面的位置
(?<!p) 不是p后面的位置
1 | var result = "hello".replace(/(?=l)/g, '#'); |
例子汇总二:
1 | // 不匹配任何东西 |
三、圆括号的作用
1 | // 分组 |
反向引用
1 | //反向引用 \1应该代表的第一个分组 |
非捕获括号
之前出现的括号,都会捕获它们匹配到的数据,以便后续引用,因此也称它们是捕获型分组和捕获型分支。
1 | var regex = /(?:ab)+/g; |
例子汇总三:
1 | // 字符串trim |
四、回溯
本质上就是深度优先搜索算法。其中退到之前的某一步这一过程,我们称为“回溯”。从上面的描述过程中,可以看出,路走不通时,就会发生“回溯”。即,尝试匹配失败时,接下来的一步通常就是回溯。
个人理解,回溯,就是因为量词有时候匹配了太多内容,导致后面的字符匹配不上,然后匹配过的结果又要一个一个的回退,看是否能够与后面的正则内容匹配上。
1 | var reg = /".*"/ |
五、正则表达式的拆分
1. 结构和操作符
操作符描述 | 操作符 | 优先级 |
---|---|---|
转义符 | \ | 1 |
括号和方括号 | (…)、(?:…)、(?=…)、(?!…)、[…] | 2 |
量词限定符 | {m}、{m,n}、{m,}、?、*、+ | 3 |
位置和序列 | ^、$、\元字符、一般字符 | 4 |
管道符(竖杠) | | | 5 |
2. 匹配字符串整体问题
因为是要匹配整个字符串,我们经常会在正则前后中加上锚 ^ 和 $。
1 | var reg = /^abc|bcd$/ |
3. 量词连缀问题
1 | // var reg = /^[abc]{3}+$/ 会报错 |
4. 元字符转义问题
1 | // 所有结构里,用到的元字符总结如下: |
不要无所不用正则,正则也有它的局限。
比如一个大正则,使用多个小正则。
注意带有g修饰符的正则在使用match和test时,会有lastIndex这个数值,并不会每次都从同一个位置进行测试
使用具体型字符组来代替通配符,来消除回溯
5. 使用具体型字符组来代替通配符,来消除回溯
匹配字符串 123”abc”456 中的 “abc”。
/“.*”/,会在第 3 阶段产生 4 次回溯
/“.?”/,会产生 2 次回溯
要使用具体化的字符组,来代替通配符.,以便消除不必要的字符,此时使用正则 /“[^”]“/,即可
6. 使用非捕获型分组
捕获分组需要内存来保存它们。不需要使用分组引用和反向引用时,此时可以使用非捕获分组。
例如,/^[-]?(\d.\d+|\d+|.\d+)$/ 可以修改成:/^[-]?(?:\d.\d+|\d+|.\d+)$/。
7. 独立出确定字符
例如,/a+/ 可以修改成 /aa*/。
因为后者能比前者多确定了字符 “a”。这样会在第四步中,加快判断是否匹配失败,进而加快移位的速度。
8. 提取分支公共部分
比如,/^abc|^def/ 修改成 /^(?:abc|def)/。
又比如, /this|that/修改成 /th(?:is|at)/。
这样做,可以减少匹配过程中可消除的重复。
9. 减少分支的数量,缩小它们的范围
/red|read/ 可以修改成 /rea?d/。
此时分支和量词产生的回溯的成本是不一样的。但这样优化后,可读性会降低的。
六、正则表达式编程
1. 正则的四种操作
验证
1 | var regex = /\d/; |
切分
1 | var regex = /,/; |
提取
1 | // match |
替换
1 | var string = "2017-06-26"; |
2. 相关API
String#search
String#split
String#match
String#replace
RegExp#test
RegExp#exec
search 和 match,会把字符串转换为正则的。
1 | var string = "2017.06.27"; |
3. match 返回结果的格式
与正则对象是否有修饰符 g 有关。
1 | var string = "2017.06.27"; |
4. exec 比 match 更强大
正则实例的两个方法 exec、test,当正则是全局匹配时,每一次匹配完成后,都会修改 lastIndex,如果没有 g,自然都是从字符串第 0 个字符处开始尝试匹配。
1 | var string = "2017.06.27"; |
5. test 整体匹配时需要使用 ^ 和 $
1 | console.log( /123/.test("a123b") ); |
6. split 相关注意事项
1 | var string = "html,css,javascript"; |
7. replace 是很强大的
replace 有两种使用形式,这是因为它的第二个参数,可以是字符串,也可以是函数。
当第二个参数是字符串时,如下的字符有特殊的含义:
属性 描述
$1,$2,…,$99 匹配第 1-99 个 分组里捕获的文本
$& 匹配到的子串文本
$` 匹配到的子串的左边文本
$’ 匹配到的子串的右边文本
$$ 美元符号$$
1 | var result = "2,3,5".replace(/(\d+),(\d+),(\d+)/, "$3=$1+$2"); |
8. 修饰符
修饰符 描述
g 全局匹配,即找到所有匹配的,单词是 global。
i 忽略字母大小写,单词是 ingoreCase。
m 多行匹配,只影响 ^ 和 $,二者变成行的概念,即行开头和行结尾。单词是 multiline。
1 | var regex = /\w/img; |
正则作用:
正则:用来处理字符串的规则
匹配:判断一个字符串是否符合我们制定的规则
1
2
3
4var reg = /\d/; //包含一个0-9之间的数字
console.log(reg.test('start')); //false
console.log(reg.test('1')); // true
console.log(reg.test('start5435')); //true捕获:把字符串中符合我们正则规则的内容捕获到 -> exec:reg.exec(str)
1
2
3var reg = /\d/; //包含一个0-9之间的数字
console.log(reg.exec('start')); // null
console.log(reg.exec('1')); // ["1", index: 0, input: "1", groups: undefined]
如何创建正则:
正则的两种创建方式是有区别的,每一个正则表达式都是由元字符和修饰符组成的
1 | //字面量方式: |
- 具有特殊意义的元字符
元字符 | 在//之间具有意义的一些字符 |
---|---|
\ | 转义字符,转译后面字符所代表的含义 |
^ | 以某个元字符开始(匹配中是不占位置的) |
$ | 以某个元字符结尾(匹配中是不占位置的) |
\n | 匹配一个换行符 |
. | 除了\n以外的任意字符 |
() | 分组 把一个大正则本身划分成几个小正则 |
\d | 一个0-9之间的数字 [0-9] |
\D | 除了0-9之间的数字以外的任何字符 |
\b | 匹配一个边界符 |
\w | 数字、字母、下划线中的任意一个字符 [0-9a-zA-Z_] |
\s | 匹配一个空白字符,空格、制表符、换页符… |
x | y |
[xyz] | x、y、z中的一个 |
[^xyz] | 除了三个以外的任何一个字符 |
[a-z] | a-z之间的任何一个字符 |
[^a-z] | 除了a-z之间的任何一个字符 |
- 代表出现次数的量词元字符
量词元字符 | 含义 |
---|---|
* | 出现零到多次 |
+ | 出现一到多次 |
? | 出现或者不出现 |
{n} | 出现n次 |
{n,} | 出现n到多次 |
{n,m} | 出现n到m次 |
1 | var reg = /^\d$/; //只能是一个0-9之间的数字 |
注意
[]
- 在中括号中出现的所有字符都是代表本身意思的字符
- 中括号中不识别二位数
1
2var reg = /^[12-68]$/; //1、2-6或者8中的一个
var reg = /^[\w-]]$/; //数字、字母、下划线、-中的一个
()
- 改变x|y的默认优先级
- /a+/ 表示连续出现 a ,而需要连续出现 ab,则需要 /(ab)+/
- 在多选分支结构(p1|p2)中,括号的作用是提供了子表达式的所有可能
- 一般情况下分组的作用是为了引用或者重复出现分组或者分支结构
1
2
3
4
5
6
7
8
9
10var reg = /^i love (javascript|regular expression)$/
console.log(reg.test('i love javascript'));
console.log(reg.test('i love regular expression'));
// true
// true
reg = /(\d{4})-(\d{2})-(\d{2})/
var reg = /^18|19$/;
//18、19、181、1819、119、18
var reg = /^(18|19)$/;
//18、19x
正则的应用
有效数字的正则 正则、负数、零、小数
1
2
3
4//"."可以出现也可以不出现,一旦出现,就必须跟一位或多位数字
//最开始可以有+/-也可以没有
//整数部分,一位数可以是0-9之间的一个,多位数不能以0开头
var reg = /^[+-]?(\d|([1-9]\d+))(\.\d+)?$/正则的创建方式
在字面量中,//之间包起来的所有字符都会是元字符,有的有特殊意义,大部分都代表本身含义的普通元字符
1
2
3
4
5
6
7
8
9
10
11var name = "attacki";
var reg = /^\d+"+name+"\d+$/;
reg.test('2019attacki2019'); //false
reg.test('2019"""nameeee"2019'); //true
//对于这种正则需要用实例创建方式
var reg = new RegExp("^\\d+"+ name + "\\d+","g");
reg.test('2019attacki2019'); //true
// 字面量方式和实例创建的方式在正则中的区别?
// 字面量方式中出现的一切都是元字符,所以不能进行变量拼接。
// 而实例创建方式是可以的,字面量方式直接写\d就可以,而在实例中需要把它转义 \\d年龄介于18-65 18-19 20-59 60-65
1
var reg = /(1[8-9]|[2-5]\d|6[0-5])/;
验证邮箱
1
var reg = /^[\w.-]+@[0-9a-z-A-Z]+(\.[A-Za-z]{2,4}){1,2}$/;
中国标准真实姓名 2-4位
1
var reg = /^[\ue400=\u9fa5]{2,4}$/
身份证号码
1
2
3var reg = /^\d{17}(\d|X)$/
//正则的捕获
var reg = /^(\d{2})(\d{4})(\d{4})(\d{2})(\d{2})(\d{2})(\d)(\d|X)$/;把数字替换为汉字数字
1
2
3
4
5var str = "20190328";
var ary =['零','一','二','三','四','五','六','七','八','九'];
str = str.replace(/\d/g,function(){
return ary[arguments[0]];
})获取一个字符串中出现次数最多的字符,并且获取出现次数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17var str = "jesus love person, he is the son of god";
var obj ={};
str.replace(/\w|,/gi,function(){
var val = arguments[0];
obj[val] >= 1 ? obj[val]++ : obj[val] = 1;
});
//获取最多的次数
var maxNum = 0;
for(var key in obj){
obj[key] > maxNum ? maxNum = obj[key]:null;
}
//把所有符合出现maxNum次数的都获取到
var ary = [];
for(var key in obj){
obj[key] === maxNum ? ary.push(key):null;
}
console.log(obj,ary);模版引擎实现的初步原理
1
2
3
4
5var str = 'my name is {0} ,my love is {1}';
var ary = ["attacki",'miao'];
str = str.replace(/{(\d+)}/g,function(){
return ary[arguments[1]];
});把字符串中所有单词首字母变为大写
queryUrlParamter
1
2
3
4
5
6
7var str = 'http://kbs.sports.qq.com/game?mid=1000&cid=1454&app=1.0';
var reg =/([^?=&]+)=([^?=&]+)/;
var obj = {};
str.replace(reg,function(){
obj[arguments[1]] = arguments[2];
})
console.log(obj);时间格式化
1
2
3
4
5
6
7
8
9
10
11
12
13var str = "2015-5-16 14:53:00";
var reg = /^(\d{4})[-/](\d{1,2})[-/](\d{1,2}) +(\d{1,2}):(\d{1,2}):(\d{1,2})$/;
var ary = [];
str.replace(reg,function(){
ary = ([].slice.call(arguments)).slice(1,7);
})
//设定好时间格式,把数组中内容替换到指定区域
var resStr = "{0}年{1}月{2}日 {3}时{4}分{5}秒";
str = str.replace(/{(\d+)}/g,function(){
var val = ary[arguments[1]];
val.length === 1 ? val='0'+val:null;
return val;
})数据类型检测
1
2
3
4
5
6
7
8
9
10
11
12
13
14typeof 用来检测数据类型的运算符
console.log(typeof 12);
//typeof返回的都是字符串,其中包含对应数据类型
// string number boolean undefined function object
//局限性,不能具体细分是数组、正则还是对象中的其他值。 typeof null === "object"
//使用逻辑或、逻辑与 替代typeof
function(num1,num2){
if(typeof num2==='undefined'){num2 = 0};
num2 = num2 || 0;
}
function(callback){
if(typeof callback==='function'){callback()};
callback && callback();
}
instanceof 检测某一个实例是否属于某个类
1 | var obj = []; |
constructor 构造函数,先找私有的,私有没有再找原型
1 | //constructor 和 instanceof非常相似 |
Object.prototype.toString.call()
1 | //Object.prototype.toString他的作用是返回当前方法执行主题(方法中this)所属类的信息 |
toString的理解
1 | 对于Number、String、Boolean、Array、RegExp、Date、Function原型上的toString方法都是把当前数据类型转化为字符串。 |
exec 正则的捕获
捕获的内容格式,匹配成功才有内容,匹配失败结果为null
捕获的内容是一个数组。
第一项是当前大正则捕获的内容,
index:捕获内容在字符串中开始的索引位置
input:捕获的原始字符串正则捕获的特点
懒惰性:每次执行exec只捕获第一个匹配的内容,在不进行任何处理的情况下,再执行多次捕获,捕获的还是第一个匹配内容。
正则里面有lastIIndex属性:是正则每次捕获字符串开始查找的位置,默认是01
2
3
4
5
6var reg = /\d+/;
var str = "AE86AE";
var res = reg.exec(str); //["86", index: 2, input: "AE86AE", groups: undefined]
console.log(reg.lastIndex);// 0
res = reg.exec(str); //["86", index: 2, input: "AE86AE", groups: undefined]
console.log(reg.lastIndex);// 0 说明第二次捕获还是从0开始查找的去除正则的懒惰性,可以在正则后面加修饰符
修饰符 g、i、m global(g) 全局匹配 ignoreCase(i) 忽略大小写 multiple(m) 多行匹配 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var reg = /\d+/g;
var str = "AE8666666AE89AE";
var res = reg.exec(str);
console.log(reg.lastIndex);// 9
res = reg.exec(str);
console.log(reg.lastIndex);// 13
//把所有捕获内容放在数组当中
var str2 = 'AE86AE87AE89';
var ary = [];
var res = reg.exec(str2);
while(res){
ary.push(res[0]);
res = reg.exec(str2);
}
console.log(ary);//["86", "87", "89"]正则的每一次捕获都是按照匹配最长的结果捕获的。
例如:8符合正则,86、87、89也符合,默认捕获的就是86、87、89
取消正则的贪婪性,在量词元字符后面加”?”
?在正则中有很多作用:
放在普通元字符后面代表出现或者不出现
放在量词元字符后面代表取消捕获的贪婪性1
2
3
4var reg =/\d+?/g;
//字符串中的match方法 ->把所有符合正则匹配的字符都捕获到
var str2 = 'AE86AE87AE89';
var ary = str2.match(reg); // ["8", "6", "8", "7", "8", "9"]
正则分组捕获
- 改变分组优先级
- 分组引用
\1代表和第一个分组出现一模一样;\2代表和第二个分组出现一模一样;
1 | var reg = /(\w)\1(\w)\2/; |
分组捕获 正则捕获的时候,可以把大正则和小分组匹配的内容分别捕获
(?:) 在分组中?:的意思是只匹配不捕获
1 | var str = "411325199301270437"; |
replace:把原有的字符替换为新字符
在不使用正则的情况下,每当执行一次只能替换一次
1 | var str = "lovejesus2018lovejesus2019"; |