正则表达式
在工作中似乎用到的正则不多,那可能是因为正则太过于抽象,不方便记忆和理解,导致大家对其使用不多,但是正则确实能够快速实现我们的功能,在工作中表单需要做很多校验,使用正则确实会为我们带来很多的便利
regular expression : RegExp
用来处理字符串的规则
-
只能处理字符串
-
一个规则: 可以验证字符串是否符合某个规则(test),也可以把字符串符合规则的内容捕获到(exec / match...)
创建方式
// 字面量创建 (两斜杠之间 就是描述规则的元字符)
let reg1 = /\d+/;
// 构造函数创建 (两个参数: 元字符字符串 修饰符字符串)
let reg2 = new RegExp("\\d+");
//区别
//1. 构造函数因为传递的是字符串,\需要写两个才代表斜杠
let reg = /\d+/g;
let reg1 = new RegExp("\\d+","g");
//2. 两斜杠之间 就是描述规则的元字符, 如果有变量,则需要构造函数方式
let type = "majun"
let reg2 = /^@"+type+"@$/
console.log(reg2.test("@majun@")) //false
console.log(reg2.test('@"""typeeee"@')) //true
let reg3 = new RegExp("^@"+type+"@$")
console.log(reg3.test("@majun@")) //true
正则表达式组成部分 (元字符 修饰符)
元字符
1. 量词元字符 设置出现的次数
* 0到多次
+ 1到多次
? 0或1次
{n} n次
{n,} n到多次
{n,m} n到m次
2. 特殊元字符 单个或组合在一起 代表特殊含义
\ 转义字符(普通->特殊->普通)
. 除\n(换行符)以外的任意字符
^ 开始
$ 结束
\n 换行符
\d 0-9
\D
\w 数字 字母 _
\W
\s 空白字符(空格、制表符、换页符等)
\S
\t 制表符 (一个TAB键:四个空格)
\b 边界
x|y x或者y
[xyz] x或者y或者z
[^xy] 除了x y
[a-z] 指定a-z这个范围中的任意字符 [0-9a-zA-Z_] === \w
[^a-z]
() 分组
(?:) 只匹配不捕获
(?=) 正向预查
(?!) 负向预查
3. 普通元字符:代表本身含义
/mj/ 匹配的就是 "mj"
修饰符
1. i ignoreCase 忽略大小写匹配
2. m multiline 可以进行多行匹配
3. g global 全局匹配
元字符详细解析
^ $
let reg = /^\d/
console.log(reg.test("mj")) //false
console.log(reg.test("2021mj")) //true
let reg1 = /\d$/
console.log(reg1.test("mj")) //false
console.log(reg1.test("mj2021")) //true
// 都不加 包含
let reg2 = /\d+/
// 都加 字符串只能是和规则一致的内容
let reg3 = /^\d+$/
console.log(reg3.test("1mj2")) //false
console.log(reg3.test("12")) //true
\
// . 代表除\n外的任意字符
let reg = /^2.3$/;
console.log(reg.test("2.3")); //true
console.log(reg.test("2@3")); //true
console.log(reg.test("23")); //false
// 基于转义字符 \.代表小数点
let reg1 = /^2\.3$/;
console.log(reg1.test("2.3")); //true
console.log(reg1.test("2@3")); //false
let str = "\\d";
let reg = /^\d$/;
console.log(reg.test(str)) //fasle
reg = /^\\d$/
console.log(reg.test(str)) //true
x|y
ler reg = /^18|29$/;
console.log(reg.test("18")) //true
console.log(reg.test("29")) //true
console.log(reg.test("189")) //true
console.log(reg.test("129")) //true
console.log(reg.test("1829")) //true
console.log(reg.test("829")) //true
console.log(reg.test("182")) //true
//直接x|y会存在很乱的优先级问题,一般配合小括号分组
reg = /^(18|29)$/;
// 18 29
[xyz]
// 中括号中出现的字符一般代表本身的含义
let reg = /^[@+]$/;
console.log(reg.test("@")) //true
console.log(reg.test("+")) //true
console.log(reg.test("@@")) //false
console.log(reg.test("@+")) //false
reg = /^[\d]$/;
console.log(reg.test("\\")) //false
console.log(reg.test("d")) //false
console.log(reg.test("9")) //true
reg = /^[\\d]$/;
console.log(reg.test("\\")) //true
console.log(reg.test("d")) //true
console.log(reg.test("9")) //false
// 中括号只有一位
reg = /^[18]$/;
console.log(reg.test("1")) //true
console.log(reg.test("8")) //true
console.log(reg.test("18")) //false
reg = /^[10-29]$/
// 1 或者 0-2 或者 9
常用的正则表达式
- 是否是有效数字
/*
* 可能出现 + -, 也可能不出现
* 一位0-9都可以,多为首位不能为0
* 小数部分可能有可能没有,一旦有后面必须是小数点+数字
*/
reg = /^[+-]?(\d|([1-9]\d+))(\.\d+)?$/
- 验证密码
// 数字 字母 下划线
// 6-16位
let val = userPassInp.value
let reg = /\w{6,16}/
let flag = reg.test(val)
function checkpass () {
if(val.length < 6 || val.length > 16){
flag = false
return
}
let area = ['a','b',...,'_']
for(let i=0;i<val.length;i++){
if(!area.includes(val[i])){
return flag = false
}
}
}
- 验证真实姓名
/*
* 汉字 /^[\u4E00-\u9FA5]$/
* 名字长度 2-10
* 可能有译名 ·汉字 (·[\u4E00-\u9FA5]{2,10}){0,2}
*/
reg = /^[\u4E00-\u9FA5]{2,10}(.[\u4E00-\u9FA5]{2,10}){0,2}$/
- 验证邮箱
reg = /^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/
// \w+((-\w+)|(\.\w+))*
// 1. 开头是数字字母下划线(1到多次)
// 2. 还可以是 -数字字母下划线 或者 .数字字母下划线 整体0到多次
// 邮箱的名字由 数字 字母 下划线 - . 几部分组成,但是-/.不能连续出现且不能作为开始
// @[A-Za-z0-9]+
// 1. @后面紧跟着:数字 字母 (1到多次)
// ((\.|-)[A-Za-z0-9]+)*
// 1. 对@后面名字的补充
// \.[A-Za-z0-9]+
// 1. @xxx.com @xxx.cn 这个匹配的才是最后的域名(.com/.cn/.org/.edu/.net)
ma.jun@datatom.com
ma.jun191@iwhaleicloud.com
- 验证身份证
// 18位 最后一位可能为X
// 前六位 省市县
// 中间八位 年月日
// 最后四位 最后一位: X|\d 倒数第二位:偶数 女 奇数 男 其余经过算法算出来
let reg = /^\d{17}(\d|X)$/
//小括号分组的第二个作用:分组捕获
reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/
reg.exec('421127199504130075') //["421127199504130075", "421127", "1995", "04", "13", "7", "5", index: 0, input: "421127199504130075", groups: undefined]
正则的捕获
实现正则捕获的方法
-
正则RegExp.prototype上的方法
exec
test
-
字符串String.prototype上支持正则表达式处理的方法
replace
match
split
...
/*
* 基于exec实现的正则捕获
* 1. 捕获到的结果就是null或者一个数组
* 第一项:匹配到的内容
* 其余项:对应分组匹配到的内容
* index: 匹配到内容的起始索引
* input: 字符串原始值
* 2. 每次执行exec,只能捕获到一个符合正则规则的,都是第一个 (正则捕获的懒惰性)
*/
let str = "majun2021up2022up2023";
let reg = /\d+/;
console.log(reg.exec(str)) //['2021',index: 5, input: 'majun2021up2022up2023', groups: undefined]
//实现正则捕获的前提是:正则和字符串要匹配,不匹配返回null
let reg = /^\d+$/;
console.log(reg.test(str)) //false
console.log(reg.exec(str)) //null
// 懒惰性原因
// reg.lastIndex 正则下一次匹配的起始索引 默认为0
let str = "majun2021up2022up2023";
let reg = /\d+/;
console.log(reg.lastIndex) //0
console.log(reg.exec(str)) //['2021'...]
console.log(reg.lastIndex) //0
即使手动修改
lastIndex
也不起作用,我们可以加个修饰符g,浏览器默认会修改lastIndex,下一次从上一次结束的位置开始查找,所以可以得到后面内容
接下来实现一个execAll方法能够一次捕获到所有匹配内容
(function(){
RegExp.prototype.execAll = function execAll (str) {
// str 要匹配的字符串
// this 正则实例
// 防止没有加g 每次res都为第一项的数组 产生死循环
if(!this.global) return this.exec(str)
let resultArr = [],
res = this.exec(str);
while (res) {
// 只要捕获的内容不为null 继续捕获
resultArr.push(res[0])
res = this.exec(str);
}
return resultArr
}
})()
字符串的
match
方法就可以捕获到所有内容
正则的分组捕获
(?:)
只匹配不捕获,像上面身份的捕获一样,因为\d|X 加了一个分组改变优先级,导致分组捕获到
let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/
console.log(reg.exec('421127199504130075')) //["421127199504130075", "421127", "1995", "04", "13", "7", "5", index: 0, input: "421127199504130075", groups: undefined]
console.log('421127199504130075'.match(reg)) //["421127199504130075", "421127", "1995", "04", "13", "7", "5", index: 0, input: "421127199504130075", groups: undefined]
let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(?:\d|X)$/
console.log(reg.exec('421127199504130075')) //["421127199504130075", "421127", "1995", "04", "13", "7", index: 0, input: "421127199504130075", groups: undefined]
console.log('421127199504130075'.match(reg)) //["421127199504130075", "421127", "1995", "04", "13", "7", index: 0, input: "421127199504130075", groups: undefined]
不设置g只匹配一次,exec和match获取的结果一样(既有大正则匹配的内容,也有小分组匹配的内容)
let str = "{0}年{1}月{2}日"
let reg = /\{(\d+)\}/
console.log(reg.exec(str)) //["{0}", "0", index: 0, input: "{0}年{1}月{2}日", groups: undefined]
console.log(str.match(reg)) //["{0}", "0", index: 0, input: "{0}年{1}月{2}日", groups: undefined]
设置g exec只能一次一个,但是match可以捕获所有,但是问题是没有捕获小分组内容
let str = "{0}年{1}月{2}日"
let reg = /\{(\d+)\}/g
console.log(reg.exec(str)) //["{0}", "0", index: 0, input: "{0}年{1}月{2}日", groups: undefined]
console.log(str.match(reg)) //["{0}", "{1}", "{2}"]
let aryBig = [],
arySmall = [],
res = reg.exec(str)
while (res) {
let [big, small] = res
aryBig.push(big)
arySmall.push(small)
res = reg.exec(str)
}
正则的分组引用
let str = "good"; // book foot seek look
let reg = /^[a-zA-Z]([a-zA-Z])\1[a-zA-Z]$/
正则捕获的贪婪性
贪婪性:正则捕获的时候,是按照当前正则匹配最长的结果来匹配的
let str = "majun2021up2022up2023"
let reg = /\d+/g
console.log(str.match(reg)) //["2021", "2022", "2023"]
reg = /\d+?/g
console.log(str.match(reg)) //["2", "0", "2", "1", "2", "0", "2", "2", "2", "0", "2", "3"]
做人不能像正则,又懒惰又贪婪
其他捕获方法
test
本意是匹配也能捕获,从字符串左侧开始匹配,匹配到符合正则规则的字符,返回true,否则返回false,同样的test在修饰符g下也会修改lastIndex的值
当test匹配到结尾或者匹配不到时,返回false,成功则向数组添加当前小分组匹配第一个元素内容 在RegExp的constructor中存在
$1
-$9
,他们的具体指的是当前本次匹配小分组第一到第九捕获的内容
replace
字符串方法 替换 str.replace(str|reg, str|function)
let time = "2021-04-28"
let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/
time = time.replace(reg, "$1年$2月$3日")
console.log(time) //"2021年04月28日"
time = "2021-04-28"
time = time.replace(reg, (big,$1,$2,$3)=>{
console.log(big,$1,$2,$3) //2021-04-28 2021 04 28
return "$1年$2月$3日"
})
console.log(time) //"2021年04月28日"
// 单词首字母大写
let str = "good good study, day day up"
let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g
str = str.replace(reg, (...arg)=>{
let [big, $1] = arg
return $1.toUpperCase() + big.substring(1)
})
// 字符串中哪个字母出现的次数最多,多少次?
let str = "abcgajgajjagjlagj"
let obj = {}
let str1 = [...str]
str1.forEach(item=>{
if(obj.hasOwnProperty(item)) {
obj[item]++
return
}
obj[item] = 1
})
console.log(obj) //{a:5,b:1,c:1,g:4,j:5,l:1}
let max = -Infinty;
for(let key in obj){
obj[key] > max ? max=obj[key] : null
}
let res = []
for(let key in obj){
if(obj[key]===max){
res.push(key)
}
}
console.log(`出现最多的是${res},${max}次`)
// -----------------------
let str = "abcgajgajjagjlagj"
let str1 = [...str]
str1 = str1.sort((a,b)=>a.localeCompare(b)).join('')
//str1 = str1.sort().join('')
let reg = /([a-zA-Z])\1*/g
let ary = str1.match(reg).sort((a,b)=>b.length - a.length)
console.log(ary) //["aaaaa", "jjjjj", "gggg", "b", "c", "l"]
let max = ary[0].length
let res = [ary[0].substr(0,1)]
for(let i=1;i<ary.length;i++){
if(ary[i].length < max) {
break
}
res.push(ary[i].substr(0,1))
}
console.log(`出现最多的是${res},${max}次`)
// --------------------
let str = "abcgajgajjagjlagj"
let str1 = [...str]
str1 = str1.sort((a,b)=>a.localeCompare(b)).join('')
let max = 0,
res = [],
flag = false;
for(let i=str1.length;i>0;i--){
let reg = new RegExp("([a-zA-Z])\\1{" + (i-1) + "}", "g")
str1.replace(reg, (content,$1)=>{
res.push($1)
max=i
flag = true
})
if(flag) break
}
console.log(`出现最多的是${res},${max}次`)
常用正则处理
~function () {
function formatTime(template = "{0}年{1}月{2}日 {3}时{4}分{5}秒") {
let ary = this.match(/\d+/g);
template = template.replace(/\{(\d+)\}/g, (...arg)=>{
let [, $1] = arg
let time = ary[$1] || '00'
// time = time.length < 2 ? '0' + time : time
time.length < 2 ? time = '0' + time : null
return time
})
return template
}
function queryURLParams() {
let res = {}
this.replace(/([^?=&#]+)=([^?=&#]+)/g, (...[, $1, $2])=>{
res[$1] = $2
})
this.replace(/#([^?=&#]+)/g, (...[, $1])=>{
res['hash'] = $1
})
return res
}
function millimeter() {
return this.replace(/\d{1,3}(?=(\d{3})+$)/g, content=>{
return content + ","
})
}
['formatTime','queryURLParams','millimeter'].forEach(item=>{
String.prototype[item] = eval(item)
})
}()
var a = "2021-4-28-15-25-1"
console.log(a.formatTime()) //2021年04月28日 15时25分01秒
console.log(a.formatTime("{0}/{1}/{2} {3}/{4}/{5}")) //2021/04/28 15/25/01
var b = "https://www.baidu.com?m=1&j=2#pathName"
console.log(b.queryURLParams()) //{m: "1", j: "2", hash: 29}
var c = "12345678"
console.log(c.millimeter()) //12,345,678