JS 中正则基本概念梳理
1 . 什么是正则表达式 ?
正则表达式(Regular Expression),又称规则表达式,是用于匹配 字符串 中字符组合的模式
- 只能用来处理字符串
- 可以用来验证字符串是否符合某个规则(test),也可以用来获取字符串中符合规则的内容(exec)
- 字符串原型方法上处理正则表达式的常用方法(replace,match,split)
2.正则表达式由哪些东西组成 ?
正则表达式由 修饰符 和 元字符 组成
- 元字符:分为普通元字符和特殊元字符及量词元字符,用在 // 里(/元字符/修饰符)
- 修饰符:用在 //外(/元字符/修饰符)
2.1普通元字符
数字和大小写字母
2.2特殊元字符
| 字符 | 含义 |
|---|---|
| ^ | 匹配输入的开始。如果多行标志被设置为 true,那么也匹配换行符后紧跟的位置。 |
| $ | 匹配输入的结束。如果多行标志被设置为 true,那么也匹配换行符前的位置。 |
| \ | 转义字符 |
| . | (小数点)默认匹配除换行符之外的任何单个字符 |
| \n | 换行符 |
| \d | 0~9之间的数字 |
| \D | 除了0~9 之外的任意字符 |
| \w | 数字、字母、下划线(小写w) |
| \W | 除了 数字、字母、下划线 的任意字符 |
| \s | 一个空白字符(包含空格、制表符、换行符等 |
| \t | 一个制表符(一个TAB键:4个空格 |
| \b | 匹配一个单词的边界 |
| x|y | x或y |
| [xyz] | x或y或z |
| [^xy] | 除了xy的任意字符 |
| [a-z] | a~z 的任意字符 小写的英文字母 |
| [^a-z] | 除了a-z 的任意字符 |
| () | 1.提升优先级 2.分组捕获 3.分组引用 |
| (?:) | 只匹配不捕获 |
| (?=) | 正向肯定预查 |
| (?!) | 正向否定预查 |
| ..... | 还有很多,写不动了 |
2.3 量词元字符
| 字符 | 含义 |
|---|---|
| * | 匹配前一个表达式 0 次或多次 |
| + | 匹配前面一个表达式 1 次或者多次 |
| ? | 匹配前面一个表达式 0 次或者 1 次 |
| {n} | 前面的字符连续出现 n 次 |
| {n,m} | 前面的字符连续出现的范围是 n~m 次 |
| {n,} | 前面的字符连续出现 n ~ n+ 次 |
| ...... | 我还能写,这里只列举以上最常见的几种 |
3.正则表达式该如何创建 ?
假设我们要创建一个用来匹配数字的表达式
- 字面量方法 : let reg = /\d+/g
- new 实例方法 : let reg = new RegExp("\\d+","g") // 字符串中我们需要多加个 \ 来转译 \
4.正则表达式如何使用?
4.1 test 的使用
正则.test(需要检测的字符串),匹配则返回 true , 不匹配则返回 false
4.1.1 注册常用的验证
1.校验用户名
涉及知识点 :
- ^ : 匹配输入的开始
- $ : 匹配输入的结束
- [xyz]:x或y或z
- {n,m} : 前面的字符连续出现 n ~ m 次
let name = "彭于晏";
let regName = /[\u4E00-\u9FA5]{2,}/; // 中国人的名字,两个汉字起步嘛
regName.test(name) // true (思考:那少数名族的名字呢? 弗拉基米尔·弗拉基米罗维奇·普京)
let name2 = "弗拉基米尔·弗拉基米罗维奇·普京"
let regNanmes = /^[\u4E00-\u9FA5]{2,10}(·[\u4E00-\u9FA5]{2,10}){0,2}$/
regNanmes.test(name2)
2.校验邮箱
涉及知识点 :
- \w : 数字、字母、下划线
- * : 前面的字符出现 0 ~ 多次
- + : 前面的字符出现 1 ~ 多次
- \ : 转译有特殊含义的字符
- x | y : x 或 y
- () : 第一个作用提升优先级
/** @前面部分 + @ + @后面部分
* 1. 普通邮箱:112cbc@qq.com 112c.b.c@qq.com 112c-b-c@qq.com
* 2. 企业邮箱:cbc@cn-suning.com.cn
**/
let regEmail = /^\w+((-\w+)|(\.\w+))*@[0-9a-zA-z]+((\.|-)[0-9a-zA-z]+)*\.[0-9a-zA-z]+$/ //
let str = "c.b.c@163cn-suning.com.cn"
regEmail.test(str) //true
4.2 正则捕获 exec 的使用
正则.exec(需要匹配的字符串), 返回符合正则的字符串内容
- 数组中第一项:本次捕获到的内容
- 其余项:对应小分组本次单独捕获的内容
- index:当前捕获内容在字符串中的起始索引
- input:原始字符串
- groups:存储分组具名化后内容
4.2.1 . 身份证号码的校验及捕获
涉及知识点 :
- () 的第二个作用:分组捕获
- (?:) 只匹配不捕获
- ?<name> 分组具名化处理
/*
* 18 位身份证号码都是数字,只有最后一位是数字或X
*/
let reg1 = /^\d{17}(\d|X)$/
let str = "32068120300728663X"
reg1.test(str) // true 我们难道只想知道她填的身份证号码对不对吗?不想了解下她所在的城市,出生年月吗?好,请看下面分析
let reg2 = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/
reg2.exec(str)
// 我们获取最后一位好像也没啥意义,那我们改改代码吧
let reg3 = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(?:\d|X)$/
reg3.exec(str)
//通过数组的下标来拿有时候会出错吧,那我把需要用到的内容存储在一个分组中,并为每个元素赋上一个通俗易懂的名字是不是更好呢,那我们再改改代码
let reg4 = /^(?<area>\d{6})(?<year>\d{4})(?<month>\d{2})(?<date>\d{2})\d{2}(?<sex>\d)(?:\d|X)$/
reg4.exec(str)
4.2.2 . 获取字符串中的数字
涉及知识点 :
- 正则捕获的贪婪性
- 正则捕获的懒惰性
- 如何解决这两个特性的缺点
let str = "今年是 2020 年,我才 18 岁;我今天大概写了123456 6行代码";
let reg1 =/\d+/;
reg1.exec(str)
// 上图中,我们是不是拿到了 2020 ,那我的 18 去拿了呢,不能因为我 谎报年龄就不给我捕获了吧,想想办法,再捕获一次
reg1.exec(str)
// 不够靓仔?,再想想办法
let reg2 =/\d+/g; // 修饰符 g, 全局捕获
reg2.exec(str)
// 没有依旧啊,不行,我必须得证明我是 18 岁,我要再捕获一次
reg2.exec(str)
// 终于证明了自己,彪子,放俩意大利炮庆祝下,~~~~~~~~
// 稍等,稍等,我今天写了 123456 这么多行代码吗?这么优秀吗?我数了数,只写了 6 行啊,那我该怎么体现我数代码行数没数错呢?
let str = "我今天大概写了123456 6行代码";
let reg3 =/\d+?/g;
reg3.exec(str)
- 总结上述问题:
- 在没有全局捕获的情况下,每次执行 exec,都是捕获的第一次出现的数字,index 也始终是第一次捕获数字的位置;在我们加了 g 全局修饰符后,再次执行,就能捕获到第二个数字 ,index 也发生了变化,所以当需要捕获的内容在字符串中多次出现时,我们无法一次捕获返回全部的值,需要执行多次捕获方法,这就是正则的懒惰性
- 默认情况下,正则捕获的时候,是按照当前正则所匹配的最长结果来获取的,而不是入我们数数般的一个个获取,这就是正则的贪婪性
正则那么懒,我们可不应该这么懒啊,取其精华,弃其糟粕,我们高级 CV 工程师讲究的都是一把梭,上哪能搞一个一次捕获全局返回的方法多好,那么方法来了
RegExp.prototype.execAll = function(str = ""){
// 正则没有加 g 全局匹配修饰符,则无论执行多少次捕获,结果始终是第一次捕获的内容
if(!this.global) return this.exec(str)
let execArr = [],
res = this.exec(str);
while(res){
execArr.push(res[0])
res = this.exec(str);
}
return execArr.length === 0 ? null : execArr
}
reg2.execAll(str)
这样我们就能一次性拿到字符串中所有的数字了
4.3 既然正则是用来处理字符串的,那字符串也为我们提供了捕获的方法
- match
- replace
- split
4.3.1 match 方法的使用
- 字符串.match(正则) 返回符合匹配正则的字符串数组
let str = "今年是 2020 年,我才 18 岁";
str.match(/\d+/g)
我们使用 match 一次性的获取到了字符串中的所有数字,它是不是相当于给我执行了多个 exec 呢?,是不是和我们上面实现的 execAll 很相似呢? 所以上面所实现的 execAll 代码,基本就是 match 的底层实现原理了
4.3.2 replace 方法的使用
//把时间字符串转换成我们想要的格式
//我想把日期变成 2020年7月21日 10时36分38秒
方法一 :
1. 我先根据空格把 年月日和时分秒分开
2. / 分割拿到 年月日
3. : 分割拿到 时分秒
4.开始拼接大法
function formatTime(){
let dateStr = new Date().toLocaleString('chinese', { hour12: false }); // 2020/7/21 10:36:38
let tempSplit = dateStr.split(' '); // ["2020/7/21", "10:36:38"]
let tempSplit1 = tempSplit[0].split('/'); // ["2020", "7", "21"]
let tempSplit2 = tempSplit[1].split(':'); //["10", "36", "38"]
return `${tempSplit1[0]}年${tempSplit1[1]}月${tempSplit1[2]}日 ${tempSplit2[0]}时${tempSplit2[1]}分${tempSplit2[2]}秒`
}
// 那如果我只想要年月日,只想要时分秒;只想要 日 时分秒呢? 我想要 2020-7-21 10-36分3-秒呢,脑壳疼不疼?,疼也要搞啊,咋搞?正则大法好
方法二:咱给出默认格式,由用户指定想要的格式,想要啥给你返回啥
String.prototype.formatTime = function(str = "{0}年{1}月{2}日 {3}时{4}分{5}秒"){
// 拿到年月日时分秒 ["2020", "07", "21", "11", "08", "31"]
let arr = this.match(/\d+/g).map(item =>{
return item.length < 2 ? '0' + item : item
})
// 使用正则,拿到 {index} ,根据 index 拿到上 arr 中的内容进行替换
return str.replace(/\{(\d+)\}/g,(content,index)=>{
return arr[index] || '00'
})
}
let dataString = new Date().toLocaleString('chinese', { hour12: false });
dataString1 = dataString.formatTime('{2}日 {3}时{4}分');
console.log(dataString1); // 21日 11时01分
dataString2 = dataString.formatTime('{0}.{1}.{2} {3}.{4}.{5}');
console.log(dataString2)
4.3.3 split 方法的使用
let names = "Stephen Curry ;Klay Thompson ; Draymond Green ; Andrew Wiggins ";
let re = /\s*(?:;|$)\s*/;
let nameList = names.split(re);
console.log(nameList); // ["Stephen Curry", "Klay Thompson", "Draymond Green", "Andrew Wiggins", ""]
5. | \b (?=) (?!) 等一些知识点的练习题
5.1 | 练习题
let reg = /12|34/ // 含 12 或 34
console.log(reg.test(1)) // false
console.log(reg.test(2)) // false
console.log(reg.test(3)) // false
console.log(reg.test(4)) // false
console.log(reg.test(12)) // true
console.log(reg.test(34)) // true
console.log(reg.test(124)) // true
console.log(reg.test(234)) // true
console.log(reg.test(1234)) // true
let reg = /1(2|3)4/ //含有 1 ,2 或者 3 ,4
console.log(reg.test(1)) // false
console.log(reg.test(2)) // false
console.log(reg.test(3)) // false
console.log(reg.test(4)) // false
console.log(reg.test(12)) // false
console.log(reg.test(34)) // false
console.log(reg.test(123)) // false
console.log(reg.test(124)) // true
console.log(reg.test(1234)) // false
let reg = /^12|34/ // 12 或者 34 开头
console.log(reg.test(1)) // false
console.log(reg.test(2)) // false
console.log(reg.test(3)) // false
console.log(reg.test(4)) // false
console.log(reg.test(12)) // true
console.log(reg.test(34)) // true
console.log(reg.test(123)) // true
console.log(reg.test(124)) // true
console.log(reg.test(1234)) // true
let reg = /^12|34$/ // 12 或者 34 开头, 12 或者 34 结尾
console.log(reg.test(1)) // false
console.log(reg.test(2)) // false
console.log(reg.test(3)) // false
console.log(reg.test(4)) // false
console.log(reg.test(12)) // true
console.log(reg.test(34)) // true
console.log(reg.test(123)) // true
console.log(reg.test(124)) // true
console.log(reg.test(1234)) // true
let reg = /^(12|34)$/ // 12 或者 34
console.log(reg.test(1)) // false
console.log(reg.test(2)) // false
console.log(reg.test(3)) // false
console.log(reg.test(4)) // false
console.log(reg.test(12)) // true
console.log(reg.test(34)) // true
console.log(reg.test(123)) // false
console.log(reg.test(124)) // false
console.log(reg.test(1234)) // false
/*
* () 的第三个作用: 分组引用
*/
let str = '1001'
let reg = /^\d(\d)\1\d$/ // ()\1 代表引用一份前面的分容
reg.test(str)
5.2 \b 练习题
// 涉及知识点: \b ,处理边界问题
let str = "Complaining does not solve anything";
let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g;
str = str.replace(reg,(item,first)=>{
first=first.toUpperCase();
item=item.substring(1);
return first+item;
});
console.log(str) // "Complaining Does Not Solve Anything"
5.3 (?=)正向肯定预查:表示对后面边界的肯定匹配要求
let reg = /iPhone\s(?=X|XR|11)/i
let str1 = "iPhone X"
let str2 = "iPhone 2G"
str1.match(reg) // ["iPhone ", index: 0, input: "iPhone X", groups: undefined]
str2.match(reg) // null
5.4 (?!)正向否定预查:表示对后面边界的否定匹配要求
let reg = /iPhone\s(?!X|XR|11)/i
let str1 = "iPhone X"
let str2 = "iPhone 2G"
str1.match(reg) // null
str2.match(reg) // ["iPhone ", index: 0, input: "iPhone 2G", groups: undefined]
5.5 正则中 test 的本意是用来匹配字符串是否符合规则,其实它也可以用来捕获,只是很少用
- RegExp.$&:是获取当前大正则的内容
- RegExp.$1~9:获取当前本次正则匹配后,第一个到第九个分组的信息
let str = '{1}A{2}B{3}C'
let reg = /\{(\d+)\}/g;
console.log(reg.test(str)) // true
console.log(RegExp.$1) // 1
console.log(reg.test(str)) // true
console.log(RegExp.$1) // 2
console.log(reg.test(str)) // true
console.log(RegExp.$1) // 3
console.log(reg.test(str)) // false
console.log(RegExp.$1) // 3 // 存储的是上次捕获的内容
本文小伙只写了部分皮毛,希望各位 dalao 赐教,下次争取写点毛皮。