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