一、正则介绍
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑
正则的引擎大致可分为两类:DFA和NFA
- DFA (Deterministic finite automaton) 确定型有穷自动机
- NFA (Non-deterministic finite automaton)非确定型有穷自动机,大部分都是NFA。
例子: 比如有字符串this is muchen’s blog,正则表达式为 /mu(shen|chen|chem)/;
NFA先在字符串中查找 m 然后匹配其后是否为 u ,如果是 u 则继续,查找其后是否为 s 如果不是则匹配其后是否为 c (此时淘汰shen选择支)。然后继续看其后是否依次为 h,e,接着测试是否为 n ,是 n 则匹配成功,不是则测试是否为 m 。NFA工作方式是以正则表达式为标准,反复测试字符串,这样同样一个字符串有可能被反复测试了很多次!
DFA会从 this 中 t 开始依次查找 m,定位到 m ,已知其后为u,则查看表达式是否有 u ,此处正好有u 。然后字符串u 后为c ,DFA依次测试表达式,此时 shen 不符合要求淘汰。chen 和 chem 符合要求,然后DFA依次检查字符串,检测到che 中的 n 时只有chen 分支符合,则匹配成功!
两种引擎的工作方式完全不同,一个(NFA)以表达式为主导,一个(DFA)以文本为主导;
二、正则基础
语法:
/正则表达式主体/修饰符(可选)
修饰符:
修饰符 | 说明 |
---|---|
i | 执行对大小写不敏感的匹配 |
g | 执行全局匹配(查找所有匹配而非在找到第一个匹配后停止) |
m | 执行多行匹配 |
n | Unicode模式 |
y | 全局匹配(区别于g) |
元字符(拥有特殊含义的字符)
元字符 | 描述 |
---|---|
· | 查找单个字符,除了换行符和行结束符 |
\w | 查找单词字符 |
\W | 查找非单词字符 |
\d | 查找数字(同 /[0-9]/) |
\D | 查找非数字字符(同 /[^0-9]/) |
\s | 查找空白字符 |
\S | 查找非空白字符 |
\b | 匹配单词边界 |
\B | 匹配非单词边界 |
\0 | 查找NULL字符 |
\n | 查找换行符 |
\f | 查找换页符 |
\r | 查找回车符 |
\t | 查找制表符 |
\v | 查找垂直制表符 |
\xdd | 查找以十六进制dd规定的字符 |
\uxxxx | 查找以十六进制数xxxx规定的Unicode字符 |
量词(用于表示重复次数的含义)
量词 | 描述 |
---|---|
n+ | 匹配任何包含至少一个n的字符串 |
n* | 匹配任何包含零个或多个n的字符串 |
n? | 匹配任何包含零个或一个n的字符串 |
n{X} | 匹配包含X个n的序列的字符串 |
n{X,} | X是一个正整数,前面的模式n连续出现至少X次时匹配 |
n{X,Y} | X和Y为正整数,模式n连续出现至少X次,至多出现Y次时匹配 |
n$ | 匹配任何结尾为n的字符串 |
^n | 匹配任何开头为n的字符串 |
括号修饰符(用于查找某个范围内的字符)
表达式 | 描述 |
---|---|
[abc] | 查找方括号中的所有字符 |
[^abc] | 查找任何不在方括号之间的字符 |
[0-9] | 查找任何从0到9的数字 |
[a-z] | 查找任何从小写a到小写z之间的字符 |
[A-z] | 查找任何从大写A到小写z之间的字符 |
[red | blue | green] | 查找任何指定的选项(中括号内,竖线表示普通的字符) |
a(bc) | 匹配abc字符 |
a(b | c) | 匹配ab或ac字符串 |
(\n) | 获取第n个捕获组里面的内容 |
//正则表达式中的小括号"()",是代表分组的意思。 如果再其后面出现\1则是代表与第一个小括号中要匹配的内容相同
let dateList = `
2017-10-10
2017-11-2017
2017-12-12
`
dateList.match(/^(\d{4})-(\d{2})-(\1)/gm)
>>> ["2017-11-2017"]
dateList.match(/^(\d{4})-(\d{2})-(\2)/gm)
>>> ["2017-10-10", "2017-12-12"]
/^(\d)\1{2}([a-z])\2{2}/g.test('111aaab')
>>> true
捕获组(用变量来调用匹配到的值)
捕获组 | 描述 |
---|---|
(\d+) | 括号中的被称之为捕获组 |
(? |
命名name捕获组(新方法) |
非捕获组(规则会被命中,但是在结果中不会包含它)
非捕获组 | 描述 |
---|---|
?=n | 匹配任何其后紧接指定字符串n的字符串 (正向前瞻) |
?!n | 匹配任何其后没有紧接指定字符串n的字符串 (负向前瞻) |
?<=n | 匹配任何其前紧接指定字符串n的字符串 (正向后瞻 - 部分浏览器支持度不好) |
?<!n | 匹配任何其前没有紧接指定字符串n的字符串 (负向后瞻 - 部分浏览器支持度不好) |
- 前瞻是非捕获性的:其特征是无法引用。
- 前瞻不消耗字符:前瞻只匹配满足前瞻表达式的字符,而不匹配其本身。
ReExp对象方法let str = "my name is <muchen>, i like <guitar> and <music>!";
str.match(/<.+?>/g)
>>> ["<muchen>", "<guitar>", "<music>"]
str.match(/(?<=<).+?(?=>)/g)
>>> ["muchen", "guitar", "music"]
方法 | 描述 |
---|---|
exec | 检索字符串中指定的值,并确定其位置 |
test | 检索字符串中指定的值,返回true或false |
toString | 返回正则表达式的字符串值 |
let str = 'man an woman';
let reg = /man/g;
console.log(str.replace(reg, "person"))
>>> person an woperson
let reg = /man/g;
let reg1 = /(wo)?man/g;
reg.compile(reg1);
console.log(str.replace(reg, "person"))
>>> person an person
/[chen]/.exec('my name is muchen')
>>> ["n", index: 3, input: "my name is muchen", groups: undefined]
// groups用来存储命名捕获组的信息
let _reg = /(?<first>mu)(?<second>chen)/
_reg.exec('my name is muchen')
>>> (3) ["muchen", "mu", "chen", index: 11, input: "my name is muchen", groups: {…}]
>>> 0: "muchen"
>>> 1: "mu"
>>> 2: "chen"
>>> groups: {first: "mu", second: "chen"}
>>> index: 11
>>> input: "my name is muchen"
>>> length: 3
>>> __proto__: Array(0)
_reg.exec('my name is muchen').groups
>>> {first: "mu", second: "chen"}
/my/ig.exec('My name is muchen my')
>>> ["My", index: 0, input: "My name is muchen my", groups: undefined]
/[chen]/.test('my name is muchen')
>>> true
var reg = /(\d{4})-(\d{2})-(\d{2})/;
var dateStr = '2021-03-11';
reg.test(dateStr);
>>> true // 执行后下面的代码才会有效
RegExp.$1 >>> "2021"
RegExp.$2 >>> "03"
RegExp.$3 >>> "11"
// 使用例子
let format = function(format) {
// eg:format="yyyy-MM-dd hh:mm:ss";
var o = {
"M+" :this.getMonth() + 1, // month
"d+" :this.getDate(), // day
"h+" :this.getHours(), // hour
"m+" :this.getMinutes(), // minute
"s+" :this.getSeconds(), // second
"q+" :Math.floor((this.getMonth() + 3) / 3), // quarter (季度)
"S" :this.getMilliseconds()
}
if (/(y+)/.test(format)) {
// RegExp.$1 : 取正则表达式中第一个分组匹配到的内容
format = format.replace(RegExp.$1, (this.getFullYear() + "") .substr(4 - RegExp.$1.length));
}
for ( var k in o) {
if (new RegExp("(" + k + ")").test(format)) {
format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
}
}
return format;
}
/muchen/g.toString()
>>> "/muchen/g"
String对象的方法
方法 | 描述 |
---|---|
search | 检索与正则表达式相匹配的值 |
match | 找到一个或多个正则表达式的匹配 |
replace | 替换与正则表达式匹配的字符 |
split | 把字符串分割为字符串数组 |
// search 返回与正则表达式查找内容匹配的第一个子字符串的位置, 没有匹配返回-1
'my name is muchen'.search(/muchen/)
>>> 11
// match 在字符串内检索指定的值,或找到一个或多个正则表达式的匹配
'my name is muchen my'.match(/my/)
>>> ["my", index: 0, input: "my name is muchen my", groups: undefined]
'My name is muchen my'.match(/my/ig)
>>> ["My", "my"]
// groups用来存储命名捕获组的信息
"2012-03-11".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/).groups
>>> {year: "2012", month: "03", day: "11"}
// 通过RegExp.$1获取值
'My name is muchen my'.match(/(m\w{1})\s(is)/)
>>> RegExp.$1 ---> "me"
>>> RegExp.$2 ---> "is"
// replace 用指定的字符串替换字符串中与正则表达式匹配的子字符串
'my name is muchen'.replace(/muchen/,"taoYeah")
>>> "my name is taoYeah"
"hi muchen, my name is muchen too".replace(/muchen/, 'taoyeah')
>>> "hi taoyeah, my name is muchen too"
"hi muchen, my name is muchen too".replace(/muchen/g, 'taoyeah')
>>> "hi taoyeah, my name is taoyeah too"
'2021/03/11'.replace(/(\d{4})\/(\d{2})\/(\d{2})/, '$2-$3-$1')
>>> "03-11-2021"
'03-11-2021'.replace(/(?<month>\d{2})-(?<day>\d{2})-(?<year>\d{4})/, "$<year>-$<month>-$<day>")
>>> "2021-03-11"
// replace 的第二个参数是函数时:
// 1、正则没有分组的时候,传进去的第一个实参是正则捕获到的内容,第二个参数是捕获到的内容在原字符串中的索引位置,第三个参数是原字符串(输入字符串)
'my name is muchen'.replace(/m(y|u)/g, function(...args) {
console.log(args)
return args[0].toUpperCase();
})
>>> ["my", "y", 0, "my name is muchen"]
>>> ["mu", "u", 11, "my name is muchen"]
>>> "MY name is MUchen"
// 2、当正则有分组的时候,第一个参数是总正则查找到的内容,后面依次是各个子正则查找到的内容
'12ab34cd567efg8h'.replace(/(\d)(\d)/g,function(...args){
console.log(args)
return Number(args[1])+Number(args[2]);
});
>>> ["12", "1", "2", 0, "12ab34cd567efg8h"]
>>> ["34", "3", "4", 4, "12ab34cd567efg8h"]
>>> ["56", "5", "6", 8, "12ab34cd567efg8h"]
>>> "3ab7cd117efg8h"
'my name is muchen'.split(' ')
>>> ["my", "name", "is", "muchen"]
'my name is muchen'.split(' ', 2)
>>> ["my", "name"]
exec和match的区别
- exec是正则表达式的方法,不是字符串的方法,它的参数是字符串;
- match是字符串执行匹配正则表达式规则的方法,他的参数是正则表达;
如果定义正则表达对象为全局匹配,match执行了全局匹配查询,而exec只会找到一个匹配的即返回;若不是全局匹配,则两者一样;
let reg = /ab/g;
let str = "1abc2,3abc4";
console.log(reg.exec(str));
console.log(str.match(reg));
>>> ["ab", index: 1, input: "1abc2,3abc4", groups: undefined]
>>> (2) ["ab", "ab"]
let reg = /ab/;
let str = "1abc2,3abc4";
console.log(reg.exec(str));
console.log(str.match(reg));
>>> ["ab", index: 1, input: "1abc2,3abc4", groups: undefined]
>>> ["ab", index: 1, input: "1abc2,3abc4", groups: undefined]
当正则表达式有子表达式时,并且定义为全局匹配,exec和match执行的结果不一样,此时match将忽略子表达式,只查找全匹配正则表达式并返回所有内容;非全局匹配,exec和match执行的结果是一样;
let reg = /a(b)/g;
let str = "1abc2,3abc4";
console.log(reg.exec(str));
console.log(str.match(reg));
>>> ["ab", "b", index: 1, input: "1abc2,3abc4", groups: undefined]
>>> ["ab", "ab"]
let reg = /a(b)/;
let str = "1abc2,3abc4";
console.log(reg.exec(str));
console.log(str.match(reg));
>>> ["ab", "b", index: 1, input: "1abc2,3abc4", groups: undefined]
>>> ["ab", "b", index: 1, input: "1abc2,3abc4", groups: undefined]
贪婪模式与非贪婪模式
- 贪婪模式——在匹配成功的前提下,尽可能多的去匹配 (默认模式);
- 非贪婪模式——在匹配成功的前提下,尽可能少的去匹配;
```
let str = “my name is
, i like and !”; // <—贪婪模式—> str.match(/<.+>/g) [“
, i like and “] // ————————————————————————————- // 先根据正则的第一个字符 < 进行寻找匹配,后面的 .可以匹配除换行符外的 // 全部字符,于是它就一直匹配到了最后,直到文本结束
- <……………………………….
“my name is
, i like and !” // .号匹配完毕后,开始匹配后面的 >,于是正则/<.+>/开始往回匹配感叹号 >, // 一直匹配到 ! 后面的 >,发现符合规则了,于是匹配出来的就是这一串字符串 // [ , i like and ] - <……………………………..>
“my name is
, i like and !” // ========================================================= // <—非贪婪模式—> str.match(/<.+?>/g) [“
“, “ “, “ “] // ————————————————————————————- // 与贪婪模式一样先匹配 <,然后进行.的匹配,但是与贪婪模式不同的是,它会 // 以最小的.的重复数进行匹配。每匹配一次.,就会往后匹配一次 > ; - <……>
“my name is
, i like and !”
// 因为g是全局匹配,所以又会从正则头开始匹配第一个 <,到了
[“muchen”, “guitar”, “music”] ``` 新增正则修饰符 u、y
ES6 对正则表达式添加了 u 修饰符,含义为 “Unicode模式”,用来正确处理大于 \uFFFF 的Unicode字符。也就是说,会正确处理四个字符的 UTF-16 编码。ES6 新增了使用大括号表示 Unicode 字符,这种表示法在正则表达式中必须加上u修饰符,才能识别当中的大括号,否则会被解读为量词。
/\u{61}/.test('a') // false --> 61个u
/\u{61}/u.test('a') // true --> 字母a
/𠮷{2}/.test('𠮷𠮷') // false -->不能识别‘’
/𠮷{2}/u.test('𠮷𠮷') // true
y修饰符和g修饰符是类似的,都是全局匹配,但y修饰符有一定的匹配要求g修饰符只要剩余的字符中存在匹配即可y修饰符必须从剩余字符的第一个位置开始匹配,否则退出匹配。
let str = "aaa_aa_aaaa"
let reg_g = /a+/g
let reg_y = /a+/y
reg_g.exec(str) // aaa
reg_y.exec(str) // aaa
reg_g.exec(str) // aa
reg_y.exec(str) // null
第一次 regy.exec 的时候,匹配的是“aaa_aa_aaaa”字符,从第一个位置开始匹配并且匹配成功:aaa;
第二次 reg_y.exec 的时候,匹配的是“_aa_aaaa”字符,从第一个位置 “” 开始匹配,但匹配不成功,因此退出匹配;
通过自定义匹配位置lastIndex,进行开始匹配位置的修改:let str = "aaa_aa_aaaa";
let reg_g = /a+/g;
reg_g.exec(str) ---> ["aaa", index: 0, input: "aaa_aa_aaaa", groups: undefined]
reg_g.lastIndex ---> 3
reg_g.exec(str) ---> ["aa", index: 4, input: "aaa_aa_aaaa", groups: undefined]
reg_g.lastIndex ---> 6
reg_g.exec(str) ---> ["aaaa", index: 7, input: "aaa_aa_aaaa", groups: undefined]
reg_g.lastIndex ---> 11
// --------------------------------------------------------------------------
reg_g.lastIndex = 2
reg_g.exec(str)
>>> ["a", index: 2, input: "aaa_aa_aaaa", groups: undefined]
reg_g.lastIndex ---> 3
let str = "aaa_aa_aaaa"
let reg_y = /a+/y
reg_y.exec(str) ---> ["aaa", index: 0, input: "aaa_aa_aaaa", groups: undefined]
reg_y.exec(str) ---> null
reg_y.lastIndex ---> 0
// --------------------------------------------------------------------------
reg_y.exec(str) ---> ["aaa", index: 0, input: "aaa_aa_aaaa", groups: undefined]
reg_y.lastIndex = 4
reg_y.exec(str)
>>> ["aa", index: 4, input: "aaa_aa_aaaa", groups: undefined]
reg_y.lastIndex = 7
reg_y.exec(str)
>>> ["aaaa", index: 7, input: "aaa_aa_aaaa", groups: undefined]