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