一、学习正则
regular expression: RegExp 用来处理字符串的规则
- 只能处理字符串
- 他是一个规则:用来验证字符串是否符合某个规则,也可以把字符串符合规则的内容捕获到(exec/match…)
\d 0-9 任何一个数字
+ 匹配一到多个
1、匹配(test)
2、捕获(exec\match)
let str = "good good study, day day up!";let reg = /\d+/;reg.test(str) //=> falsestr = "2019-08-12"reg.exec(str); //=> ['2019',index:0,inputs:'原始字符串']
二、编写正则表达式
1、创建的方式
- 字面量方式(两个斜杠之间包起来的,都是用来描述规则的元字符) let reg1 = /d+/
- 构造函数模式创建 两个参数 : 元字符字符串,修饰符字符串 let reg2 = new RegExp(“\d+”);
2、正则表达式由两部分组成
- 元字符
- 修饰符
3、常用的元字符
3-1 量词元字符 设置出现的次数
| * | 代表 零到多次 |
|---|---|
| + | 代表 一到多次 |
| ? | 零次或者一次 |
| {n} | 出现n次 |
| {n,} | 出现n到多次 |
| {n,m} | 出现n到m次 包含n和m |
3-2 特殊元字符:单个或者组合在一起代表特殊的含义
| \ | 转义字符(普通->特殊->普通) |
|---|---|
| . | 除\n(换行符)以外的任意字符 |
| ^ | 以哪一个元字符作为开始 |
| $ | 以哪一个元字符作为结束 |
| \n | 换行符 |
| \d | 0~9之间的一个数字 |
| \D | 非0~9之间的一个数字 (大写和小写的意思是相反的) |
| \w | 数字、字母、下划线中的任意一个字符 |
| \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] | 上一个取反 |
| ( ) | 正则中的分组符号 |
| (?:) | 只匹配不捕获 |
| (?=) | 正向预查 |
| (?!) | 负向预查 |
| \1 | 前后相同匹配 o\1o 相同的两个o |
3-3 普通元字符,代表本身含义
| /xiaohua/ | 此正则匹配的就是’xiaohua’ |
|---|---|
4、常用的修饰符 i 、 m 、 g
忽略单词大小写匹配
可以进行多次匹配
global 全局匹配
|
/A/.test('lala') => false/A/i.test('lala') => true
三、元字符详细解析
1、开头结尾 ^ $
^ 匹配以数字开头let reg = /^\d/;reg.test("xiaohua")reg.test("xiaohua2019")reg.test("2019xiaohua")$ 匹配以数字结尾let reg = /\d$/;reg.test("xiaohua")reg.test("xiaohua2019")reg.test("2019xiaohua")都不加包含即可let reg = /\d/;reg.test("xiaohua")reg.test("xiaohua2019")reg.test("2019xiaohua")两个都加 只能是和规则一致的内容例子:验证手机号码let reg = /^1\d{10}$/
2、转义字符 \
只能匹配2.3let reg = /^2.3$/;reg.test('2.3') truereg.test('2@3') truereg.test('23') false原因是.此时代表除\n以外的任意字符,导致匹配2@3也能匹配上,解决方法就是加上\进行转义let reg = /^2\.3$/;reg.test('2.3') truereg.test('2@3') falsereg.test('23') falselet reg=/^\d$/reg.test('\d') falselet reg=/^\\d$/ 把特殊意思符号转成普通,字符串中\\才代表\reg.test('\\d') truelet reg=/^\\$/ 字符串中\\才代表\reg.test('\\') true
3、 x | y 、 分组()
小括号不仅可以把正则匹配信息捕获到,还可以单独捕获每个小分组的内容 exec
let reg = /^18|29$/;reg.test("18")reg.test("29")reg.test("129")reg.test("189")reg.test("1829")上面全是true直接x|y会存在很乱的优先级问题,一般我们都伴随着小括号进行分组,因为小括号改变处理的优先级reg = /^(18)|(29)$/ 只能是18、29中的一个了reg.test("18") true
4、 []
- 中括号中出现的字符一般都代表本身的含义, ```javascript 下面的@+ +就是正常的+ let reg = /^[@+]+$/ //=> 代表@或者+出现一次到多次 reg.test(“@@”) reg.test(“++”)
注意:\d 在[]中代表的还是数字的意思,并不是本身意思 let reg = /^[\d]$/ reg.test(“d”) //=> false reg.test(“12”) //=> true
let reg = /^[\d]$/ reg.test(“\d”) //=> true reg.test(“12”) //=> false
- 2. 中括号中一般代表一个单数的取值范围,不存在多位数的范围```javascriptreg=/^[1-9]$/reg.test("1") //=>truereg = /^[18]$/;reg.test("1") //=>truereg.test("8") //=>truereg.test("18") //=>falsereg = /^[10-29]$/; //=> 1 或 0-2 或者 9reg.test("1") //=>truereg.test("9") //=>truereg.test("0") //=>truereg.test("2") //=>truereg.test("10") //=>false
四、常用的正则表达式
1、验证是否为有效数字
> 规则分析1、可能出现在+-号,也可能不出现 [+-]? === (+|-)?2、一位0-9都可以,多位首位不为0 (\d|([1-9]\d+))3、小数部分可有可无,一旦有后面必须有小数点+数字 (\.\d+)?let reg = /^[+-]?(\d|([1-9]\d+))(\.\d+)?$/
2、验证密码
> 规则分析1、数字、字母、下划线2、6~16位let reg = /^[0-9a-zA-Z_]{6,16}$/let reg = /^\w{6,16}$/
3、验证真实姓名
> 规则分析1、汉字 /^[\u4E00-\u9FA5]$/2、名字长度 2~10位3、可能有译名 "尼古拉斯·赵四"let reg = /^[\u4E00-\u9FA5]{2,10}(·[\u4E00-\u9FA5]{2,10}){0,2}$/
4、验证邮箱
> 规则分析1、开头数字字母下划线(1-多位) \w+2、还可以 -数字字母下划线 或者 .数字字母下划线, 整体零到多次,邮箱的名字数字字母下划线、-、. 几部分组成,但是-和. 不能连续出现,也不能作为开始 (-\w+ | \.\w+)*3、对@后面名字补充多域名 .com.cn @[A-Za-z0-9]+企业邮箱 zxt.zhufeng-peixun.office.com ((\.|-))[A-Za-z0-9]+)*4、匹配最后的域名(.com/.cn./.org/.edu/.net...) \.[A-Za-z0-9]+let reg = /^\w+((-\w)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/
5、身份证号码
> 规则分析1、 18 位 最后一位可能是Xlet reg = /^\d{17}(\d|X)$/2、身份证前六位代表 省市县 1306253、中间八位年月日4、最后四位X或者数字倒数第二位 偶数是女 奇数是男剩下几位是算法算出来的let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/;reg.exec("13062519941001203X")// ["13062519941001203X", "130625", "1994", "10", "01", "3", "X"...]// 可以通过身份证查询省市年月日性别生日等
6、 手机号验证
// 11 位 18 | 17 | 13 | 15 开头let reg = /^1(8|7|3|5|4|6|9)\d{9}$/let reg = /^1[3456789]\d{9}$/
五、两种正则创建方式的区别
1、构造函数因为传递的是字符串, “\” 需要写两个才代表斜杠
let reg = /\d+/glet reg = new RegExp("\\d+","g")
2、正则表达式中的部分内容是变量存储的值
两个斜杠抱起来的都是元字符,如果正则需要包含某个变量的值,不能使用字面量方式创建
let type = "zhufeng"reg=/^@"+type+"@$/reg.test("@zhufeng@") //=> falsereg.test('@"""typeeee"@') //=> truereg=new RegExp("^@"+type+"@$");reg.test("@zhufeng@") //=>true
六、正则的捕获
1. 正则RegExp.prototype上面支持的正则表达式处理的方法
- test
exec
1、基于exec实现正则的捕获
捕获的结果是null或者一个数组,
数组第一项:是捕获到的内容,
其余项:是对应小分组单独捕获的内容
index:当前捕获内容在字符串中的起始索引
input:原始字符串
2、每执行一次exec,只能捕获到一个符合正则的规则,默认下,都是匹配到第一个,这是正则匹配的懒惰性
出现懒惰性的原因是lastindex匹配捕获完成后值不会改变,都是0开始,解决的方式,
匹配后改变lastindex的值,可以手动修改验证
设置全局匹配修饰符g后,第一次匹配完后,lastIndex会自己修改2. 字符串String.prototype上支持正则表达式处理的方法
replace
- match
- splite
- …
let str = "zhufeng2019yangfang2020qihang2021"let reg = /\d+/reg.test(str)reg.lastIndex //=> 0reg.exec(str) //=> 2019reg.exec(str) //=> 2019// 解决正则捕获懒惰性的方法就是:全局修饰符glet reg = /\d+/greg.lastIndex //=> 0reg.exec(str) //=> 2019reg.lastIndex //=> 11reg.exec(str) //=> 2020
3、编写一个方法execAll,执行一次可以把所有匹配的结果捕获到(前提是正则一定要设置全局修饰符g)
~function(){function execAll(str=""){//=> this:RegExp的实例(当前操作的正则)//=> 进来后的第一件事是看正则是否设置了g,不设置不能使用 dir(reg)if(!this.global) return this.exec(str)let ary = [],res = this.exec(str);while (res) {ary.push(res[0])res = this.exec(str);}return ary.length===0?null:ary}RegExp.prototype.execAll = execAll}()let str = "zhufeng2019yangfang2020qihang2021"let reg = /\d+/greg.execAll(str) //=> ["2019", "2020", "2021"]
4、字符串的match方法
match不加g也不能全部匹配到,不加g也只能匹配到第一个数组
let reg = /\d+/glet str = "zhufeng2019yangfang2020qihang2021"console.log(str.match(reg));
七、正则的分组捕获
1、分组捕获 match和exec
// 身份证号码let str = "13062519941001203X"let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(?:\d|X)$/;reg.exec(str)str.match(reg)// ["13062519941001203X", "130625", "1994", "10", "01", "3", "X" ...]//=> 第一项:大正则匹配的结果//=> 其余项:每个小分组单独匹配捕获的结果//=> 如果设置了分组(改变优先级),但是捕获的时候不需要单独捕获,可以基于?:来处理,只匹配不捕获
match和exec都可以进行大正则的匹配,match的好处是在g的模式下,match可以全部匹配到大正则的,无法匹配到小正则的分组,需要通过exec通过循环得到
let str = "{0}年{1}月{2}日"let reg = /\{(\d+)\}/;reg.exec(str)str.match(reg) //=> (2) ["{0}", "0", index: 0, input: "{0}年{1}月{2}日"...]// 不设置g只匹配一次,exec和match获取的结果一致,既有大正则匹配的信息,也有小分组匹配的信息let reg = /\{(\d+)\}/g;str.match(reg) //=> ["{0}", "{1}", "{2}"]reg.exec(str) //=> ["{0}", "0", index: 0, input: "{0}年{1}月{2}日",...]
2、”分组引用”
分组引用就是通过”\数字”让其代表和对应分组出现一摸一样的内容
let str = "book"; //=> "good"、"look"、"moon"、"foot"let reg = /^[a-zA-Z](o\1o)[a-zA-Z]$/reg.test("book") //=> truereg.test("moon") //=> true
八、正则捕获的贪婪性
正则捕获的时候是按照当前正则所匹配的最长结果来获取的
let str = "写话2019@2020庞大的";let reg = /\d+/g;str.match(reg) //=> ["2019","2020"]
取消正则的贪婪性,在量词元字符后面设置 “ ? “ , 按照正则匹配最短结果来获取
let str = "写话2019@2020庞大的";let reg = /\d+?/g;str.match(reg) //=> ["2", "0", "1", "9", "2", "0", "2", "0"]
九、正则中的问号的五大作用
- 问号左边是非量词元字符,本身代表量词元字符,出现零到一次
- 问号左边是量词元字符,取消捕获时候的贪婪性
- (?:) 只匹配不捕获
- (?=) 正向预查
- (?!) 负向预查
十、其他正则捕获的方法
1、test也能捕获, RegExp.$1 (本意是匹配)
RegExp.$1~RegExp.$9 获取当前本次正则匹配后,第一个到第九个分组的信息,这个在真实项目中不实用
let str = "{0}年{1}月{2}日"let reg = /\{(\d+)\}/greg.test(str) //=> trueRegExp.$1 //=> 0reg.test(str) //=> trueRegExp.$1 //=> 1reg.test(str) //=> trueRegExp.$1 //=> 2reg.test(str) //=> falseRegExp.$1 //=> 2 存储的是上一次捕获的结果
2、replace 字符串中实现替换的方法(一般都是伴随正则一起使用)
3、案例一:把”rock@2019|rock@2020” 中的rock替换成石头
1、把”rock@2019|rock@2020” 中的rock替换成石头
let str = "rock@2019|rock@2020";// 不用正则执行一次只能替换一次str.replace("rock","石头").replace("rock","石头")str.replaceAll("rock","石头")// 使用正则str.replace(/rock/g,"石头")
2、把”rock@2019|rock@2020” 中的rock替换成rockHello
当实现这个需求的时候会发现replace无法实现,因为每次都从最开始找(类似于正则捕获的懒惰性),没有 办法找到最后面匹配
解决方法就是用replaceAll或者正则匹配
let str = "rock@2019|rock@2020";str.replace("rock","rockHello").replace("rock","rockHello") //=> "rockHelloHello@2019|rock@2020"str.replaceAll("rock","rockHello") //=> "rockHello@2019|rockHello@2020"//=> 基于正则g可以实现str.replace(/rock/g,"rockHello") //=> "rockHello@2019|rockHello@2020"
4、案例二: 把时间字符串进行处理
把2019-08-13变成2019年08月13日
let time = '2019-08-13'let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/gtime = time.replace(reg, "$1年$2月$3日") //=> "2019年08月13日"//=> 可以这样处理 [str].replace([reg],[function])// 1、首先进行捕获,匹配到几次就会传递给函数执行几次// 2、不仅方法执行,而且还根据exec捕获的内容一致的信息,传递给该方法,大正则匹配的内容,小正则匹配的信息time=time.replace(reg,(big,...arg)=>{let [$1,$2,$3] = arg;return $1+'年'+$2+'月'+$3+'日'})
5、案例三:所有单词首字母大写
let str = "good good study, day day up"let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g;// 函数被执行6次,每次都把匹配的信息传递给函数// 每一次arg:["good","g"]str = str.replace(reg,function(...arg){let [content,$1] = arg$1=$1.toUpperCase();content = content.substring(1)return $1+content;})
6、案例四:验证一个字符串中,哪个字母出现的次数最多,多少次
let str = "hahatiantianxiangshanghhh"/*==方法一:去重思维==*/// 根据对象的key唯一原理去求出let obj = {};([]).forEach.call(str,(char)=>{if(typeof obj[char] !=='undefined'){obj[char]++return}obj[char]=1})console.log(obj)let max = 1,res=[];for(let key in obj){let item = obj[key]item > max ? max = item : null;}for(let key in obj){let item = obj[key]if(item===max){res.push(key)}}console.log(`出现次数最多的字符是${res},出现了${max}次`) ) //=> 出现次数最多的字符是a,h,出现了6次/*==方法二:对字母进行排序==*/let str = "hahatiantianxiangshanghhh"str = [...str].sort((a,b)=> a.localeCompare(b)).join(''); // "aaaaaagghhhhhhiiinnnnsttx"let ary = str.match(/([a-zA-Z])\1+/g).sort((a,b)=>b.length-a.length);let max = ary[0].length,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}次`) //=> 出现次数最多的字符是a,h,出现了6次/*==方法三:按照字母排序,然后通过倒叙的当时进行带变量的正则匹配==*/let str = "hahatiantianxiangshanghhh"str= str.split('').sort((a,b)=>a.localeCompare(b)).join(''); //=> "aaaaaagghhhhhhiiinnnnsttx"let res=[],max=0,flag=false;for(let i=str.length;i>0;i--){let reg = new RegExp("([a-zA-Z])\\1{"+(i-1)+"}","g")str.replace(reg,(big,$1)=>{max=i;res.push($1)flag = true})if(flag)break;}console.log(`出现次数最多的字符是${res},出现了${max}次`) //=>出现次数最多的字符是a,h,出现了6次/*==方法四:假设法==*/let str = "hahatiantianxiangshanghhh",max=0,letter,reg="",oldLength,letterCount,result=[];while(str!==""){oldLength = str.length;letter = str.substr(0,1)reg = new RegExp(letter,"g")str = str.replace(reg,"");letterCount = oldLength - str.length;if(letterCount>=max){max = letterCountresult.push(letter)}}console.log(`出现次数最多的字符是${result},出现了${max}次`) //=>出现次数最多的字符是a,h,出现了6次
7、案例五:时间字符串格式化
time = “2019-8-13 16:51:3”; time = “2019/8/13 16:51:3”;
转化成08月13日 16时51分、2019年08月13日
/** 时间字符串格式化方法* formaTime:时间格式化处理* @params* templete:[string] 我们最后期望获取日期格式的模版,模版规则{0}->年 {1-5}->月日时分秒* @return* [string]格式化后的时间字符串* by rockshang on 2020/11/17*/!function(){function formateTime(template="{0}年{1}月{2}日 {3}时{4}分{5}秒"){let timeAry = this.match(/\d+/g);return template.replace(/\{(\d+)\}/g,(...[,$1])=>{let time = timeAry[$1] || "00";return time.length < 2 ? "0" + time : time;});}// 扩展到内置类String.prototype上["formateTime"].forEach(item=>{String.prototype[item] = eval(item);});}();// 使用let str = "2019/8/13 16:51:3";str = "2019/8/13 16:51:3";str.formateTime() //=>2019年08月13日 16时51分03秒str.formateTime("{0}年{1}月{2}日")
8、案例六:获取路径上的参数queryURLParams
/** queryURLParams:获取URL地址问号和后面的参数信息(可能也包含HASH值)* @params* @return* [object]把所有问号参数信息以键值对的方式存储起来并且返回* by rockshang on 2020/11/17*/!function(){function queryURLParams(){let obj = {};this.replace(/([^#?&#]+)=([^#?&#]+)/g,(...[,$1,$2])=>obj[$1] = $2)this.replace(/#([^#?&#]+)/g,(...[,$1])=>obj['HASH']=$1);return obj}["queryURLParams"].forEach(item=>{String.prototype[item] = eval(item);});}();// 使用let url = "http://www.baidu.com?lx=1&from=wx#video"; //=> {lx:1,from:'wx',HASH:'video'}url.queryURLParams()
9、案例七:千分符
let num = 15628954; ==> 15,628,954
普通实现方式, 字符串反转,循环
let num = "13455533333";num = num.split('').reverse().join('');for(let i=2;i<num.length -1;i+=4){let prev = num.substring(0,i+1),next = num.substring(i+1);num = prev + "," + next;}num = num.split('').reverse().join('');console.log(num)
通过正则实现:一行代码切分千位符

/** millimeter:实现大数字的千分符处理,使用正向预查?= 只匹配不捕获* @params* @return* [string]千分符后的字符串* by rockshang on 2020/11/17*/!function(){function millimeter(){return this.replace(/\d{1,3}(?=(\d{3})+$)/g,content=>content+',')}["millimeter"].forEach(item=>{String.prototype[item] = eval(item);});}();let num = "15628954";num.millimeter()
