正则(regular expression : RegExp)
用来处理字符串的规则
主要用于: 验证(test)和捕获(exec,match…)
2种创建方式:
1. let reg = new RegExp('\d+') 可传2参数,元字符 和 修饰符
2. let reg = /\d+/
`注意字符串里的‘\’ 会被进行处理 要进行转移才能得到如下的斜杠 即‘\’`
主要组成:元字符,修饰符
- 元字符
-
- 1.量词
* 0到多次 = {0,}
+ 1到多次 = {1.}
? 0到1次 = {0,1}
{n} 出现n次
{n,} 出现n到多次
{n,m} 出现n到m次
- 1.量词
-
- 2.特殊字符
\ 转义字符 (普通《=》特殊)
. 除了\n(换行)以外的 任意字符 => [^\n]
^ 以某字符开头
$ 以某字符结尾
\n 换行符
\d 0~9之间的数字 =>[0-9] , 0|1|2|3|4|5|6|7|8|9
\D 不是0~9间的数字 =>[^0-9]
\w 单字符,数字,字母,下划线中的任一字符 => [0-9a-zA-Z_]
\W 非单字符,不是数字,字母,下划线中的任一字符 => [^0-9a-zA-Z_]
\s 一个空白字符(空格,制表,换行)=>[\t\n ]
\t 制表符(一个TAB键:4个空格)
\b 匹配一个单词的边界
x|y x,y中的某一个
[xyz] x,y,z中的某一个
[^xy] 除了x,y的任意字符
[a-z] a-z中的任意字符
[^a-z] 不是a-z中的字符
() 正则分组
(?:) 只匹配不捕获
(?=) 正向预查
(?!) 负向预查
- 2.特殊字符
-
- 3.普通元字符
/i have a dream/ 正常匹配,包含‘i have a dream’即可
- 3.普通元字符
- 修饰符(IMG)
-
i =>ignoreCase 忽略单词大小写匹配
m =>multiline 可以进行多行匹配
g =>global 全局匹配
-
/A/.test('lalala') =>false
/A/i.test('lalala') =>true
示例:
^ / $:
=> ^/$两个都不加:字符串中包含符合规则的内容即可
let reg1 = /\d+/; 包含[0-9]即可
=> ^/$两个都加:字符串只能是和规则一致的内容
let reg2 = /^\d+$/; 每位都必须是[0-9]
=> 验证手机号码(11位,第一个数字是1即可)
let reg = /^1\d{10}$/;
\ :
`有时候需要转义,把特殊含义与普通含义来回转换,要能人脑识别出特殊含义`
=> .不是小数点,是除\n外的任意字符
let reg = /^2.3$/; 2,3中间可以是任意字符
reg = /^2\.3$/; 只能是2.3
=> 匹配(\d),代表0-9的数字
reg = /^\d$/;
=> 匹配字符串\d,也就是“\\d”
reg = /^\\d$/;
| :
`或这个符号会衍生出很多复杂规则,必要时配合 ()使用`
let reg = /^18|29$/;
符合的匹配规则:
1. (2位) 18开头,或29结尾
2. (3位) 1开头,9结尾,中间2或8
reg = /^(18|29)$/;
只能是18或者29
[] :
`中括号中的字符一般都是原意,且里面没有多位数,对外也是代表一位字符`
let reg = /^[@+]$/;
代表只能是字符 @/+ 出现一次 ,量词失效
reg = /^[\d]$/;
代表0-9 出现一次,\d在中括号中还是0-9(特殊字符生效了)
reg = /^[18]$/;
1/8 出现一次
reg = /^[10-29]$/;
1或者0-2或者9,出现一次
正则应用:
1.验证有效数字
规则:
1. 开头可以是 +,-号,也可以没有 [+-]?
2. 一位数 多位数(0不能打头) (\d | [1-9]\d+)
3. 小数部分可有可无,但是点后面至少一位数字 (.\d+)?
`let reg = /^[+-]?(\d|([1-9]\d+))(.\d+)?$/`
2.验证密码
规则:
1. 6到16位数 ,字母 数字 下划线
`let reg = /^\w{6,16}$/`
3.验证姓名
规则:
1.汉字范围 '\u4E00' - '\u9FA5'
2.一般长度 2~10
3.可能有多段 xx·xx·xx
‘列奥纳多·达·芬奇’ ‘弗拉基米尔·弗拉基米罗维奇·普京’
`reg = /^[\u4E00-\u9FA5]{2,10}(·[\u4E00-\u9FA5]{1,10})*$/ `
4.验证邮箱
规则:(以@为分割)
1.开头是 字母数字下划线 0到多位数
2.之后可以出现 .\w+或者-\w+ 0到多次
3. 邮箱名字也就是@之前,可以出现 字母 数字 下划线 · -(后2个不落单)
4. @紧后面是 字母或数字 出现1到多次
5. 对4的补充, .或-后面跟字母数字 出现0到多次
6. 最后的域名,.[a-zA-Z0-9]+
eg: xyz-123.self@zhufeng-peixun-office.com
`reg = /^\w+([-.]\w+)*@[a-zA-Z0-9]+((.|-)[a-zA-Z0-9]+)*.[a-zA-Z0-9]+$/`
5.身份证号
规则:
1.一共18位 ,最后一位可能是 x
2. 身份证前六位:省市县 211481
中间八位:年月日 19981023
最后四位: 0213
最后一位 => X或者数字
倒数第二位 => 偶数 女 奇数 男
其余的是经过算法算出来的
`reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)([\dX])$/`
正则两种常见方式的区别:
-
1.
let reg = /\d+/g -
- 字面量创建,斜杠中的当作元字符处理
无法拼接外界变量进来,因为变量名都被解析为元字符
- 字面量创建,斜杠中的当作元字符处理
-
2.
reg = new RegExp("\d+","g") -
- 传递字符串会被先解析一次,\需要写两个才代表斜杠
new RegExp("^@"+type+"@$"); 可以拼接变量
reg = /^@"+type+"@$/; 无法拼接type进来
正则捕获相关方法:
-
正则RegExp.prototype上的方法
- exec
- test
-
字符串String.prototype上支持正则表达式处理的方法
- replace
- match
- splite
- …….
应用举例:
`let str = "zhufeng2019yangfan2020qihang2021";`
`let reg = /\d+/g;`
基于exec实现正则捕获:
1.得到的结果为 null 或者一个数组
2.数组第一项为匹配捕获到的整个内容, 第二项往后为小分组捕获到的内容
index:匹配到的内容在整个输入中的索引位置(全匹上就是0)
input: 原始字符串
3.每次执行exec 只能捕获到一个符合规则的,
但是不加g情况下(global为false),执行多次也只捕获第一个,就算修改lastIndex也没用。
正则捕获的懒惰性:默认只捕获第一个
懒惰性解决方案:
1.默认情况下(不加g),lastIndex代表当前正则下一次匹配的
起始索引位置,不会自动修改,手动修改了也无效,每次都从0开始。
2.加全局修饰符g即可,开启后lastIndex会自动改变,
不管是 exec,test,手动修改 ,都会生效影响下次检索起始位置
3.开启g后,exec或者test ,等到执行结果为 null/false,会把lastIndex的值归零。
给正则手写一个execAll方法,一次性捕获所有匹配项:
function execAll(str){
let arr=[]
if(!this.global) return this.exec(str)
while(res=this.exec(str)){
arr.push(res[0])
}
return arr.length>0?arr:null;
}
字符串的match方法 可以实现同样的功能
str.match(reg)
分组捕获
let str = "130828199012040112";
let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(?:\d|X)$/;
console.log(str.match(reg));
这仅仅是一次的匹配结果,因为没加g,所以结果同exec
["130828199012040112", "130828", "1990", "12","04", "1", index: 0, input: "130828199012040112"]
第一项:大正则匹配的结果
其余项:()包起,捕获的小分组
`如果在正则里用()来改变优先级,又不想分组捕获,用?:可以取消`
str = "{0}年{1}月{2}日";
reg = /\{(\d+)\}/g;
如果想在多次捕获的情况下,同时也得到小分组 match是做不到的(一旦开启了g修饰符,结果就只包含捕获的整体)
str.match(reg) =>["{0}", "{1}", "{2}"]
手写扩展方法:
let arrOuter=[],arrInner=[];
while(res=reg.exec(str)){
arrOuter.push(res[0])
arrInner.push(res[1])
}
console.log(arrOuter,arrInner)
分组第三作用:分组引用 (1.提升正则内优先级 2.分组捕获 3.分组引用)
let str = "book"; 类似的有"good"、"look"、"moon"、"foot"...
let reg = /^[a-zA-Z]([a-zA-Z])\1[a-zA-Z]$/;
\1 表示和第一个分组实际捕获的内容相同
分组引用就是通过“\数字”让其代表和对应分组出现一模一样的内容
正则的捕获贪婪性:
str = "你好2019@2020世界";
reg = /\d+/g;
console.log(str.match(reg)); 结果:["2019","2020"]
默认情况下,正则尽可能捕获最长的
可用量词后加?取消贪婪性
reg = /\d+?/g;
console.log(str.match(reg));
结果: ["2", "0", "1", "9", "2", "0", "2", "0"]
?的用法总结:
量词:元字符?,元字符出现0或1次
取消贪婪:量词?,取消捕获时贪婪性
只匹配不捕获: (?:分组内容)
正向预查:?=
负向预查:?!
其他的正则捕获
1. 依靠test,在RegExp上拿到分组信息
str = "{0}年{1}月{2}日";
reg = /{(\d+)}/g;
console.log(reg.test(str)); true
console.log(RegExp.$1); "0"
console.log(reg.test(str)); true
console.log(RegExp.$1); "1"
RegExp.$1~RegExp.$9:获取正则实例匹配后,第一个到第九个分组的信息,
每次(exec,test)捕获到后都会覆盖之前的分组。
2. 字符串的replace方法,不但可以多次匹配,还能拿到分组(完胜 exec,match)
1.第一个参数可以是正则,也可以是字符串,表示要被替换的项。
但是只有是正则且加g的情况下,才会自动全局匹配
2.第二个参数,如果是函数的话,每次匹配到就会执行一次该函数,函数拿到
的参数和exec的结果一致(匹配结果及分组)并用返回值替换匹配项。
用法示例:
1.日期字符串格式化
time = "2019-08-13"; 变为"2019年08月13日"
reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/;
`只匹配一次,里面有三个分组`
time= time.replace(reg,(...[,$1,$2,$3])=>{
return `${$1}年${$2}月${$3}日`
})
2.首字母大写
str = "good good study,day day up!";
//reg =/\b([a-zA-Z])[a-zA-Z]\b$/g
reg =/([a-zA-Z])(?:[a-zA-Z])*[a-zA-Z]/g
函数执行6次
str=str.replace(reg,(...[word,letter])=>{
return letter.toUpperCase()+word.substr(1)
})
3.验证字符串出现次数
str ='sdkjfsdkjhfssssssdasajdlkjlkpoikkkkkkkkkejowhojdfds'
max=0,res=null
map={}
while(len=str.length){
var letter=str[0]
str=str.replace(new RegExp('('+letter+')\1*','g'),'')
var num =len-str.length;
map[num]?map[num].push(letter):map[num]=[letter];
if(num>max){
max=num
res=letter
}
}
console.log(max+':',res,map[max])
4. formatTime 、 queryUrl、 thousandSign封装成库
a.日期字符串模板格式化(放在字符串原型上)
function formatTime(template='$0年$1月$2日 $3时$4分$5秒'){
let arr=this.match(/\d+/g)
return template.replace(/$(\d+)/g,(...[,index])=>{
let item=arr[index] || '00'
return item.length>=2?item:'0'+item
})
}
b.url参数拆分
function queryUrl(){
let regData=/([^?#=&]+)=([^?#=&]+)/g
regHash=/#([^?=&]+)/g
let res={}
this.replace(regData,(...[,$1,$2])=>{
res[$1]=decodeURIComponent($2)
})
this.replace(regHash,(...[,$1])=>{
res.hash=$1
})
return res
}
c.千分符
function thousandSign(){
let reg=/\d{1,3}(?=(\d{3})+$)/g
return this.replace(reg,(content)=>{
return content+','
})
}
// 批量挂载写好的方法到String原型上
['formatTime','queryUrl','thousandSign'].forEach(
(item)=>{
String.prototype[item] = eval(item)
})