JavaScript正则表达式
写在前面
正则表达式是一种字符串规则,在很多处理字符串的场合能够发挥出不可估量的强大,比如表单验证,数据替换等等。
作为一名程序员,不管是前端还是后端,都应该掌握这门技术。
此文章是本人在学习正则表达式时的一些笔记,以及封装的一些比较常用且功能强大的函数。
希望这篇文章能够帮助到其他的小伙伴。
当然,可能有些封装的不尽完美,所以,不尽完美之处,还请请私信我。我们共同进步。
正则表达式 (基础部分)
- regular expression : RegExp 正则表达式
- 作用:
- 用来处理字符串的规则
- 只能处理字符串、
- 他是一个规则 可以验证字符串是否符合某个规则(test方法),也可以把字符串中符合规则的内容捕获到(exec/match方法)
- 作用:
编写正则表达式
创建方式有两种
1//字面量的方式创建 (两个//之间包起来的都是用来描述正则规则的元字符)2let reg=/\d+/;34//实例的方式 构造函数创建 两个参数: 一个是元字符字符串 修饰符字符串5let reg1=new RegExp('\\d+');正则表达式由两部分组成
元字符
修饰符
1/* 1. 常用的元字符*/23// ==> 1.量词元字符 设置出现的次数4// 1. * 0到多次5// 2. + 1到多次6// 3. ? 0次 || 一次7// 4. {n} 出现n次 n为大于0的正整数8// 5. {n,m} 出现 n 到 m 次910/* 2.特殊元字符 : 单个或者组合在一起代表特殊的含义*/11// 1. \ 转义字符(普通字符->特殊->普通)12// 2. . 除\n(换行符)以外的任意字符13// 3. ^ 以哪一个元字符作为开始(^看瑞特符号)14// 4. $ 以哪一个元字符作为结束15// 5. \n 换行符16// 6. \d 0~9之间的一个数字17// 7. \D 非0~9之间的一个数字18// 8. \w 数字、字母、下划线 中的任意一个字符19// 9. \s 一个空白字符 (包含空格 制表符 换页符等)20// 10.\t 一个制表符 (一个TAB键:4个空格)21// 11.\b 匹配一个单词的边界22// 12.x|y x或者y中的一个字符23// 13.[xyz] x或者y或者z中的一个字符24// 14.[^xy] 除了x、y以外的字符25// 15.[a-z] 指定a到z这个范围中的任意字符 [0-9a-zA-Z_] === \w26// 16.[^a-z] 15/条的取反27// 17.() 正则中的分组28// 18.(?:) 只匹配不捕获29// 19.(?=) 正向预查30// 20.(?!) 负向预查3132/* 3.普通元字符 : 代表本身含义的 */33// /zhnegze/ 此正则匹配的就是 'zhengze'修饰符
1/* 正则表达式常用的修饰符:i m g */2// 1. i ignoreCase (一个闹尅死) 忽略单词大小写匹配3// 2. m multiline(莫体力) 忽略换行匹配 能够多行匹配4// 3. g global (阁楼布偶) 全局匹配56// /A/.test('lalala'); ===>false7// /A/i.test('lalala'); ===>true元字符详细解析
^ 开头 $ 结尾1let reg = /^\d/ ; //任意数字开头2reg.test('hahaha'); //===>false3reg.test('lalala123'); //===>false4reg.test('20191101hah'); //===>true1let reg = /\d$/ ; //任意数字结尾2reg.test('hahaha'); //===>false3reg.test('lalala123'); //===>true4reg.test('20191101hah'); //===>false1//两个都不加 : 字符串中包含符合规则的就可以2let reg = /\d+/ ; //包含数字就可以3reg.test('hahaha'); //===>false4reg.test('lalala123'); //===>true5reg.test('20191101hah'); //===>true67//两个都加 : 字符串只能和规则一致8let reg1 = /^\d+$/; //之能是以数字开头 数字结尾 的数字910//验证手机号码11let reg2 = /^1\d{10}$/;//只能以1开头 以数字结尾 中间是0~9之间的数 出现10次
\1//把特殊符号转换为 普通 符号
x|y1let reg1 = /^18|19$/;2reg.test('18'); //===>true3reg.test('19'); //===>true4reg.test('189'); //===>true5reg.test('119'); //===>true6reg.test('81'); //===>false7reg.test('819'); //===>true8//-------------直接使用会存在很乱的优先级问题 一般写的时候一般都伴随着() 因为小括号会改变处理的优先级 --> 小括号: 分组9let reg1 = /^18|19$/;10reg.test('18'); //===>true11reg.test('19'); //===>true12reg.test('189'); //===>false[]1//1.中括号中出现的字符一般都代表本身的含义2//2.中括号中不存在多位数
常用的正则表达式
常用的有效数字
1有效数字: 0 1 12 0.2 -1 -12.32/*3* 规则分析4* 1.可能出现 - + 号5* 2.一位0~9都可以,多位首位不能是06* 3.小数部分可有可无,有的话必须有小数点和数字7*/8let reg = /^[+-]?(\d|([1-9]\d+))(\.\d+)?$/;验证密码
1// 字母、数字、下划线2// 长度 : 6~16位3let reg = /^\w{6,16}$/;4reg.test(val)//返回true代表符合规则 false不符合规则验证真实姓名
1/*2* 1.汉字 /^[\u4E00-\u9FA5]$/3* 2.名字长度 2~104* 3.可能有译名 XX·XXX '尼古·哈哈'5* 4.可能有少数民族名字 2~106*/7let reg = /^[\u4E00-\u9FA5]{2,10}(·[\u4E00-\u9FA5]{2,10}){0,2}$/;验证邮箱
12/*3* 1.@符号前 \w+((-\w+)|(\.\w+))*4* 1.1.以数字 字母 下划线 开头 1到多位5* 1.2.还可以是 - 数字 字母 下划线 或者 .数字 字母 下划线 整体出现 0 到 多次6* 邮箱名:由 数字 字母 下划线 . - 组成 切必须以 数字 开头 -/. 不能连续出现7* 2.[A-Za-z0-9]+ @xxx.com8* 2.1.@后紧跟着 数字 字母 1到多位9* 3.((\.|-)[A-Za-z0-9]+)* xxx@xxx.[com].cn 匹配com 对@后面名字的补充10* 3.1.11* 4.\.[A-Za-z0-9]+ .cn .com ...12*/13let reg = /^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/;身份证号码
1/*2* 1.一共18位3* 2.最后一位可能是X X代表的是104* 身份证前六位意义: 省 市 县5* 中间八位意义: 年 月 日6* 最后四位意义: 最后一位 X或者数字7* 倒数第二位: 偶数是 女 基数是 男8* 其余位数是经过算法算出来的9*/10//let reg = /^\d{17}(\d|X)$/;不用11//小括号作用 : 1. 分组捕获 不仅可以把大正则匹配的信息捕获到,还可以单独捕获到每个小分组的内容 2.改变优先级12let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})(\d{2})(\d)(\d|X)$/;13reg.exec('4114121198902034432') //捕获结果是一个数组
#### **正则表达式捕获的懒惰性**实现正则捕获的方法 懒惰性的解决办法
正则RegExp.prototype上的方法
exec
1/*2* 基于exec实现正则的捕获3* 1. 捕获到的结果是null / 一个数组 第一项捕获到的是内容 其余项 对应小分组本次单独捕获到的内容 index项 当前捕获到的 在原字符串的起始索引 input项 原始字符串4* 2.每执行一次 exec只能捕获到一个符合正则规则的 默认情况下 即使多次捕获 捕获的结果永远是第一个 即=>正则的懒惰性 默认只捕获一个5*6* lastIndex代表当前正则下一次匹配的起始索引位置 console.log(reg.lastIndex); ===07* 正则懒惰性的原因就是因为lastIndex默认情况下是不会被修改/改变的 每次都是从起始位置开始查找 lastIndex不能手动修改不行 只能使用全局修饰符 g /\d+/g 匹配后lastIndex值会自动改变8*/
1//需求:编写一个方法execAll(),执行一次可以把所有匹配的结果捕获到(前提:正则一定要设置全局修饰符 g)23function(){4function execAll(str){5// str : 是要匹配的字符串6// this :RegExp的实例(当前操作的正则)7// 首先验证传进来的正则是否设置了全局修饰符 g 如果没有 则不进行捕获8if(!this.global) return this.exec(str);9// arr : 存储最后所有捕获的信息10// res :存储每次捕获的内容11let arr = [];12let res = this.exec(str);13while(res){14//把每次捕获的数组的第一项内容放到数组arr中15arr.push(res[0]);16//只要捕获的内容部位null 则继续捕获17res = this.exec(str);18}19return arr === 0 ? null : arr;20}21RegExp.prototype.execAll=execAll;22}();23let reg = /\d+/g;24console.log(reg.execAll(str));
- test
字符串String.prototype上支持正则表达式处理的方法
replace
match
1// 字符串.match(正则); 返回所有匹配的数 以数组的形式返回 即 返回 所有符合正则的 项 的数组 如果一项都没有匹配 则返回null2// match 可以在执行一次的情况下 捕获到所有匹配的数据 ( 前提 :正则需要加全局修饰符 g )34// 上面那些代码 就是 match 实现原理
splite
……
1//实现正则匹配的前提是:当前正则要和字符串匹配 如果不匹配 (exec)捕获的是null
正则的分组捕获
1 | /* |
2 | * ?: =>只匹配不捕获 如果设置了分组 又不想捕获到 就需要加 `?:` 来处理 |
3 | */ |
4 | let reg=/^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(?:\d|X)$/; |
5 | let str='411411199801224422'; |
6 | console.log(str.match(reg)); |
7 | //返回发结果 :["411411199801224422", "411411", "1998", "01", "22", "2", index: 0, input: "411411199801224422", groups: undefined] |
1 | /* |
2 | * ===> 获取自己想要的数据 |
3 | * 多次匹配时 只能捕获到大正则匹配到的数据 小分组的信息拿不到 |
4 | * 解决方案 |
5 | */ |
6 | let str = '{1993}年{03}月{23}日'; |
7 | let reg = /\{(\d+)\}/g; |
8 | let aryBig = []; |
9 | let arySmall = []; |
10 | let res=reg.exec(str); |
11 | while(res){ |
12 | let [big,small] = res; |
13 | aryBig.push(big); |
14 | arySmall.push(small); |
15 | res=res.exec(str); |
16 | } |
17 | console.log(aryBig,arySmall); |
18 | // 执行结果为 :aryBig==>['{1993}','{03}','{23}'] arySmall==>['1993','03','23'] |
分组的第三个作用 : 分组引用
1// 分组的第三个作用 : 分组引用2let str = 'book'; //'good' 'look' 'moon' 'foot' ......3let reg = /^[a-zA-Z]([a-zA-Z])\1[a-zA-Z]$/; //分组引用 : 就是通过 '\数字' 让其代表和对应分组出现一模一样的内容4console.log(reg.test('book'));//==>true5console.log(reg.test('deep'));//==>true6console.log(reg.test('some'));//==>false
正则捕获的贪婪性
1 | /* |
2 | * =>正则捕获的贪婪性 :默认情况下 正则捕获的时候 是按照当前正则所匹配的最长结果来获取的 |
3 | * |
4 | */ |
5 | let str = '啦啦啦2019&&加油2020'; |
6 | let reg = /\d+/g; |
7 | console.log(str.match(reg));//===>["2019", "2020"] |
8 | |
9 | //----解决办法 : ==>在量词元字符后面加上一个 `?` 表示取消正则的贪婪性(按照正则匹配的最短结果来获取)---- |
10 | let str = '啦啦啦2019&&加油2020'; |
11 | let reg = /\d+?/g; |
12 | console.log(str.match(reg));//["2", "0", "1", "9", "2", "0", "2", "0"] |
?在正则中的 五大作用
- ?左边是非量词元字符 :本身代表量词元字符 出现0到1次
- ?左边是量词 元字符 : 取消捕获时候的贪婪性
- (?:) : 值匹配不捕获
- (?=) : 正向预查
- (?!) : 负向预查
其他正则捕获的方法
test也能捕获 (本意是匹配) 一般不用 了解就好
1let str = '{2019}年{11}月{2日}';2let reg = /\{(\d+)\}/g;3console.log(reg.test(str));//==>true4console.log(RegExp.$1);//==>201956console.log(reg.test(str));//==>true7console.log(RegExp.$1);//==>1189console.log(reg.test(str));//==>true10console.log(RegExp.$1);//==>21112console.log(reg.test(str));//==>false13console.log(RegExp.$1);//==>'2' 存储的是上次捕获的结果14//RegExp.$1~$9 : 获取当前本次正则匹配后 第一个到第九个分组的信息replace 字符串中实现替换的方法 (一般都是伴随正则一起使用的) 重点
1let str = 'jiege@2019|jiege@2020';2// ==>把 'jiege' 转换为 '杰哥'3// 1. 不使用正则 执行一次只能替换一个4str=str.replace('jiege','杰哥');5console.log(str);//==>'杰哥@2019|jiege@2020'67//2. 使用正则 一次就可以完成所有匹配8str=str.replace(/jiege/g,'杰哥');9console.log(str);//==>'杰哥@2019|杰哥@2020'1011// 必须使用正则 不然不好弄1213//不使用正则 每次替换从头开始 类似于正则的懒惰性14str=str.replace('jiege','jiegehaobang').replace('jiege','jiegehaobang');15console.log(str);//==>'jiegehaobanghaobang@2019|jiege@2020'1617//使用正则18str=str.replace(/jiege/g,'jiegehaobang');19console.log(str);//==>'jiegehaobang@2019|jiegehaobang@2020'案例1:把时间字符串进行处理
1let time = '2019-11-02';2//变为 ==> 2019年11月02日3let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/;4time = time.replace(reg,'$1年$2月$3日');5console.log(time)//==>"2019年11月02日"replace 实现原理 [str].replace([reg],[function])
1let time = '2019-11-02';2//变为 ==> 2019年11月02日3let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/;45// 实现原理 [str].replace([reg],[function])6// 1.replace 首先拿reg和time进行匹配捕获,能匹配到几次就会把传递的函数执行几次 (而且是 匹配一次就执行一次)7// 2.不仅把方法执行了,而且replace还给方法传递了实参信息(是 exec捕获 的内容一致的信息:大正则匹配的信息 小分组匹配的系信息......)8// 3.在函数中 返回的是什么,就把当前大正则匹配的内容替换成什么910/*time = time.replace(reg,(big,$1,$2,$3)=>{11//$1,$2,$3是自己设置的变量12console.log(big,$1,$2,$3);//==>2019-11-02 2019 11 0213})*/1415time = time.replace(reg,(...arg)=>{16let [,$1,$2,$3]=arg;17$2.length < 2 ? $2 = `0${$2}` : null;18$3.length < 2 ? $3 = `0${$3}` : null;19return `${$1}年${$2}月${$3}日`20})21console.log(time);//==>'2019年11月02日'案例2:单词首字母大写
1let str = 'good good study,day day up!';2let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g;3//=>函数执行了6次,每一次都把正则匹配到的信息传递给函数4//=>每一次arg存的都是一个数组:['good','g']......5str = str.replace(reg,(...arg)=>{6let [content,$1]=arg;7$1 = $1.toUpperCase();8content = content.substring(1);9return $1+content;10})11console.log(str);案例3:验证一个字符串中那个字母出现的次数最多,多少次?
做法1:
1/*======去重思维=====*/2let str = '2019nianshayemeiganchneg';3//创建一个对象用来存放字符串的每一个不同的项4let obj = {};5//将字符串中的每一项都放在对象中6[].forEach.call(str,char=>{7//判断对象中有没有这个字符,有就把值加一 没有就赋值为一8if(typeof obj[char]!=='undefined'){9obj[char]++;10return;11}12obj[char]=1;13})14let max = 1;15//储存次数最多的字符的数组16let res = [];17//判断出现次数最多的字符出现的次数18for(let key in obj){19let item = obj[key];20item > max ? max = item : null;21}22//判断出现次数最多的字符23for(let key in obj){24let item = obj[key];25if(item === max){26res.push(key);27}28}29console.log(res,max) //["n"] 4 做法2:
1/*======排序=====*/2let str = '2019nianshayemeiganchnega';34str=str.split('').sort((a,b)=>a.localeCompare(b)).join('');5console.log(str)//0129aaaceeegghhiimnnnnsy6let reg = /([a-zA-Z0-9])\1+/g;7let ary = str.match(reg);8console.log(str.match(reg));//["aaaa", "eee", "gg", "hh", "ii", "nnnn"]9ary.sort((a,b)=>b.length - a.length);//sort()数组排序10console.log(ary.sort((a,b)=>b.length - a.length));//["aaaa", "nnnn", "eee", "gg", "hh", "ii"]11console.log(`出现最多的是${ary[0].slice(0,1)},出现了${ary[0].length}`);//出现最多的是n,出现了412let max = ary[0].length;13let res = [ary[0].substr(0,1)];14for (let i = 1; i < ary.length; i++) {15let item = ary[i];16if (item.length<max) {17break;18}19res.push(item.slice(0,1));20}21console.log(max,res); 做法3:*代码最少 推荐 *
1/*======从最大到最小去找--正则匹配=====*/2let str = '2019nianshayemeiganchnega';34//把字符串变成数5str = str.split('').sort((a, b) => a.localeCompare(b)).join(''); //字母的比较不能用加减 只能用a.localeCompare(b)6//接收最大值7let max = 0;8let res = [];9let flag = false;10console.log(str); //0129aaaaceeegghhiimnnnnsy11for (let i = str.length; i > 0; i--) {12let reg = new RegExp('([a-zA-Z])\\1{' + (i - 1) + '}', 'g');13str.replace(reg, (content, $1) => {14res.push($1);15max = i;16flag = true;17});18if (flag) {19break;20}21}22console.log(`出现次数最多的字符为${res},出现了${max}次`); 做法4:查找字母删减去重法
案例4:正则表达式 之 时间字符串格式化
1~ function () {2/**3* formatTime:时间字符串的格式化处理方法4* @param {String} templete 期望获取的日期格式模板5* 模板规则:{0} ->{0~5}->年月日时分秒6* @returns {String} 格式化后的时间字符串7*/8function formatTime(templete = '{0}年{1}月{2}日 {3}时{4}分{5}秒') {9//先获取时间字符串中的年月日时分秒等信息10let timeAry = this.match(/\d+/g);11console.log(timeAry); //["2019", "8", "13", "16", "51", "3"]12return templete = templete.replace(/\{(\d+)\}/g, (content, $1) => {13//content : 代表当前本次大正则匹配的信息 $1代表小粉猪单独匹配的信息14//以$1的值为索引,到timeary中找到对应的时间(如果没有 用'00'代替)15let time = timeAry[$1] || '00';16time.length < 2 ? time = `0${time}` : null;17return time;18});19}20/* 扩展到内置类String.prototype上 */21['formatTime'].forEach(item => {22String.prototype[item] = eval(item);23})24}();2526let time = '2019-8-13 16:51:3';27// 服务器获取的时间数据 :2019-8-13 16:51:3 2019/8/13 16:51:328// 想要转变的格式 :'08月13日 16时51分' '2019年08月13日' ......2930// 如果想要[time.formatTime()]这样调用,则方法必须在字符串的原型上31time.formatTime();32time.formatTime('{0}年{1}月{2}日'); //"2019年08月13日"33time.formatTime('{0}/{1}/{2}');//"2019/08/13"34time.formatTime('{0}-{1}-{2} {3}:{4}:{5}');//"2019-08-13 16:51:03" 案例4:正则表达式之qureyURLParams
1~ function () {2/**3* qureyURLParams:获取url地址问号后面的参数系信息(可能包含hash值)4* @param5* @return6* [object]把所有问号参数信息以键值对的方式存储起来并返回7*/8function qureyURLParams() {9let obj = {};10this.replace(/([^=#&?]+)=([^=#&?]+)/g, (...[, $1, $2]) => obj[$1] = $2);11this.replace(/#([^=#&?]+)/g, (...[, $1]) => obj['HASH'] = $1);12return obj;13}14/* 扩展到内置类String.prototype上 */15['qureyURLParams'].forEach(item => {16String.prototype[item] = eval(item);17})18}();1920let url = 'https://www.baidu.com/s?wd=dnf&rsv_spt=1';21url.qureyURLParams(); 案例4:正则表达式之千分符
1~ function () {2/**3* millimeter:实现大数字的千分符处理4* @param5* @return6* [String] 千分符后的字符串7*/8function millimeter() {9return this.replace(/\d{1,3}(?=(\d{3})+$)/g, content =>content + ',');10}11/* 扩展到内置类String.prototype上 */12['millimeter'].forEach(item => {13String.prototype[item] = eval(item);14})15}();1617let num = '123445112'; //=>'112,212,323,123'18num.millimeter();
注意:部分文章可能会在不就的将来更新
如果能够帮助到你,是小编最大的荣幸
当然 有 不好的地方 请大家帮忙指出 学习永无止境
小编一直认为 人外有人 天外有天 一起学习 共同进步
让我们共同加油吧!!!
原文作者: Yunjie Ge
原文链接: http://www.blog.geyunjie.com/2018/02/15/RegExp/
版权声明: 转载请注明出处(必须保留作者署名及链接)