一、正则介绍
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑
正则的引擎大致可分为两类: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-102017-11-20172017-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 wopersonlet 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')>>> truevar 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+/glet reg_y = /a+/yreg_g.exec(str) // aaareg_y.exec(str) // aaareg_g.exec(str) // aareg_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 ---> 3reg_g.exec(str) ---> ["aa", index: 4, input: "aaa_aa_aaaa", groups: undefined]reg_g.lastIndex ---> 6reg_g.exec(str) ---> ["aaaa", index: 7, input: "aaa_aa_aaaa", groups: undefined]reg_g.lastIndex ---> 11// --------------------------------------------------------------------------reg_g.lastIndex = 2reg_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+/yreg_y.exec(str) ---> ["aaa", index: 0, input: "aaa_aa_aaaa", groups: undefined]reg_y.exec(str) ---> nullreg_y.lastIndex ---> 0// --------------------------------------------------------------------------reg_y.exec(str) ---> ["aaa", index: 0, input: "aaa_aa_aaaa", groups: undefined]reg_y.lastIndex = 4reg_y.exec(str)>>> ["aa", index: 4, input: "aaa_aa_aaaa", groups: undefined]reg_y.lastIndex = 7reg_y.exec(str)>>> ["aaaa", index: 7, input: "aaa_aa_aaaa", groups: undefined]
