一、认识正则
- (RegExp)Regular Expression:是js的一个内置类
- 正则,就是一个规则,可以检验某个字符串是否符合这个规则(test),也可以把字符串中符合某个规则的字符捕获到(exec 、match…)
- 主要用来处理字符串
let reg = /\d+/; // 0-9之间的数字出现1到多次
let str = 'd12fgh';
console.log(reg.test(str)); // true
console.log(reg.exec(str)); // ["12", index: 1, input: "d12fgh", groups: undefined]
二、正则的组成
正则由元字符和修饰符组成
- 元字符:量词元字符、特殊元字符、普通元字符
- 修饰符:
- i,不区分大小写
- g,全局匹配
- m,对行匹配
ES6新增修饰符:
u
修饰符,含义为“Unicode模式”,用来正确处理大于\uFFFF
的Unicode字符。也就是说,会正确处理四个字节的UTF-16编码。 ``` 元字符:量词元字符、特殊元字符、普通元字符 量词元字符: *: 0到多次 +:1到多次 ?: 0到1次 {n}: 出现n次 {n,}: 至少出现n次 {n,m}: 出现n到m次
特殊元字符:
\ : 转义字符,可以把特殊的元字符转换为普通的元字符,也可以把普通元字符转换为特殊元字符
. :任意字符(除了换行符意外)
^:以什么什么开头
$:以什么什么结尾
\n:换行符
\d:0到9之间的任意数字
\D:非0到9之间的任意数字
\w:数字、字母、下划线
\t:制表符
\b:单词边界
\s:空白符
x|y: x和y之间的任意一个
[a-z]:a到z之间的任意一个字符
[a-zA-Z0-9]:a到z或者A到Z或者0到9之间的任意字符
[^a-z]:除了a到z之外的任意一个字符
():分组
(?:):只匹配,不捕获
(?=):正向预查
(?!):负向预查
普通元字符:
b
n
d
...
修饰符:就是对正则起到修饰作用的 i—>ignoreCase:不区分大小写 m—>multiline:多行匹配 g—>global:全局匹配
<a name="s9VzJ"></a>
### 三、正则的简单使用
<a name="NfnZv"></a>
#### 1. 开头和结尾(^ $)
- 只能匹配开头和结尾元字符内符合规则的字符串
```javascript
// 1、^ $
// let reg = /18$/; // 以18结尾就可以
// console.log(reg.test('198')) // false
// console.log(reg.test('218918')) // true
// let reg = /^18$/; // 既要以18开头,又要以18结尾
// console.log(reg.test('218918')) // false
// console.log(reg.test('18e18')) // false
// console.log(reg.test('18')) // true
// let reg = /^\d{2}$/;
// console.log(reg.test('e2')); // false
// console.log(reg.test('2e2')); // false
// console.log(reg.test('22')); // true
// console.log(reg.test('2')); // false
// 如果 ^ $都不加,那只要出现正则里的字符就可以
// 如果^ $都加,那匹配的字符必须跟正则的内容一样才可以
let reg= /^123456$/;
console.log(reg.test('000123456')); // true 只要结尾匹配上就可以
2. 转义字符()
// 2、\:转义字符
// let reg = /^2.3$/;
// . 是除了\n之外的任意字符
// console.log(reg.test('2.3')) // true
// console.log(reg.test('2e3')) // true
// console.log(reg.test('2@3')) // true
// 基于转义字符,把.转化成普通的元字符
// let reg = /^2\.3$/;
// console.log(reg.test('2.3')) // true
// console.log(reg.test('2e3')) // false
// console.log(reg.test('2@3')) // false
// let str = '中\\国'; // 转义字符在字符串里也适用
// console.log(str) // '中\国'
// let reg = /^\\d$/;
// console.log(reg.test('\\d')) // true
// 匹配手机号
1.11位数字开头
2.以数字1开头
3.第二位是3到9
let reg = /^1[3-9]{1}\d{9}$/
3. x|y : x或者y之间取一个
// let reg = /^18|29$/;
// console.log(reg.test('18')); // true
// console.log(reg.test('29')); // true
// console.log(reg.test('189')); // true
// console.log(reg.test('129')); // true
// console.log(reg.test('1829')); // true
---------------------------------------------------------------------------------------------
直接写 x|y会存在很乱的优先级问题,一般我们写的时候都伴随着小括号进行分组,因为小括号改变处理的优先级 =>小括号:分组
// let reg = /^(18|29)$/;
// console.log(reg.test('18')); // true
// console.log(reg.test('29')); // true
// console.log(reg.test('189')); // false
// console.log(reg.test('129')); // false
// console.log(reg.test('1829')); // false
//4、():分组
// 1、提高匹配的优先级
// 2、分组引用
// 3、分组捕获
// 先捕获最大的内容,然后按照分组在依次进行捕获
// let str = 'a3a';
// let reg = /[a-z](\d)([a-z])/;
// console.log(reg.exec(str)) // ['a3a','3',...]
4.匹配重复出现的字符(abab、abbc…)
let str = 'moon';
// ([a-z])\1:让第一次分组的字符在出现一次
let reg = /^[a-z]([a-z])\1[a-z]$/;
console.log(reg.test('moon'));
console.log(reg.test('mwsn'));
let str = 'abab';
// \1 表示第一次分组捕获的字符串
// \2 表示第二次分组捕获的字符串
let reg = /^([a-z])([a-z])\1\2$/ // 让第一次分组和第二次分组的字符各自再出现一次
console.log(reg.test('abab'))
console.log(reg.test('mnmn'));
console.log(reg.test('aaaa'))
console.log(reg.test('moon'))
let str = 'foood';
// 让第一次分组的字符在出现两次
let reg = /^[a-z]([a-z])\1\1[a-z]$/;
console.log(reg.test('foood')) // true
console.log(reg.test('fonod')) // true
5.[] 中不允许出现多位数
let reg = /^[21-68]$/; // 2 或者 1-6 或者8 之间取一个数
console.log(reg.test('21')); // false
console.log(reg.test('68')); // false
console.log(reg.test('2')); // true
console.log(reg.test('5')); // true
console.log(reg.test('8')); // true
let reg = /^[@#ws]$/; // 中括号中的字符出现一次即可
6.正则的正负向预查
- 正向预查:?=
- 负向预查:?! ```javascript
// 正向预查 // zhufeng后面必须跟着peixun才能匹配成功 let reg = /zhufeng(?=peixun)/; console.log(reg.test(‘zhufengpeixunwwwwwww’)); // true
// 负向预查: // zhufeng后面必须不跟着peixun才能匹配成功 let reg = /zhufeng(?!peixun)/; console.log(reg.test(‘zhufengwwwwwww’)) // true console.log(reg.test(‘zhufengpeixunwww’)) // false
<a name="Id0ux"></a>
### 四、正则的应用举例
<a name="8Lvuz"></a>
#### 1. 匹配有效数字
```javascript
// 匹配有效数字
// 2 2.5 -2.5 +2.5 0 2.4
1、+-可有可无,而且在开头, [+-]?
2、有可能是一位数,也有可能是多位数 \d [1-9]\d+
3、小数 (\.\d+)?
let reg = /^[+-]?(\d|[1-9]\d+)(\.\d+)?$/;
2. 匹配密码
密码:
1、6到18位
2、数字、字母、下划线
let reg = /^\w{6,18}$/i;
// 如果不使用正则,那校验密码的就会变得很麻烦
function fn(str) {
if (str.length < 6 || str.length > 18) {
alert('密码不符合要求');
return;
}
let ary = ['0', '1', 'a', '_', '...'];
for (var i = 0; i < str.length; i++) {
if (ary.includes(str[i])) {
alert('密码不符合要求')
return
}
}
}
3.匹配中文名字
匹配名字
宋小宝
爱新觉罗·溥仪
阿诺德·施瓦辛格·溥仪
// [\u4E00-\u9FA5] 代表汉字
let reg = /^[\u4E00-\u9FA5]{2,6}(·[\u4E00-\u9FA5]{1,6}){0,2}$/;
console.log(reg.test('爱新觉罗·溥仪')) // true
console.log(reg.test('阿诺德·施瓦辛格·溥仪')) // true
console.log(reg.test('阿·施瓦辛格·溥仪')) // false
4.匹配身份证号
/*
身份证号
1、18位
2、前六位:省市区
3、7到14位是生日
4、最后四位
前两位:同年同月同日生的排序
第三位:奇数是男孩,偶数是女孩
最后一位:有可能是数字,还有可能是X
年 [1-2]/d{3}
月 01-09 10-12 1[0-2]
日 01-09 10-29 30-31
*/
let reg = /^\d{17}(\d|X)$/;
let reg1 = /^(\d{6})(\d{8})(\d{2})(\d{1})(?:\d|X)$/;
let reg2 = /^(?:\d{6})([1-2]\d{3})(0[1-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])(?:\d{2})(\d{1})(?:\d|X)$/
// '130981 1935 06 30 6018'
// console.log(reg2.test('13098119350630601X'));
// console.log(reg2.test('1309813935063060182'));
// console.log(reg2.test('1309811935383060182'));
let res = reg2.exec('13098119350630601X');
console.log(res[1] + '年' + res[2] + '月' + res[3] + '日')
console.log(res[4] % 2 === 0 ? '女' : '男')
五、正则的创建方式(字面量和构造函数创建方式)
//=>构造函数因为传递的是字符串,\需要写两个才代表斜杠
// let reg = /\d+/g;
// reg = new RegExp("\\d+", "g");
// console.log(reg); // /\d+/
//=>正则表达是中的部分内容是变量存储的值
//1.两个斜杠中间包起来的都是元字符(如果正则中要包含某个变量的值,则不能使用字面量方式创建)
// let type = "zhufeng";
// reg = /^@"+type+"@$/;
// console.log(reg.test("@zhufeng@")); //=>false
// console.log(reg.test('@"typeeeee"@')); //=>true
//2.这种情况只能使用构造函数方式(因为它传递的规则是字符串,只有这样才能进行字符串拼接)
// reg = new RegExp("^@" + type + "@$");
// console.log(reg);
// console.log(reg.test("@zhufeng@"));//=>true
六、正则的捕获
正则的捕获:把正则匹配到的内容捕获到
RegExp.prototype.exec() 方法 exec是正则上的一个方法,用来捕获符合规则的内容
let str = "asd123";
let reg = /\d{3}/;
let res = reg.exec(str);
console.log(res);// ["123", index: 3, input: "asd123", groups: undefined]
// exec:他是正则实例的一个公有属性,用来捕获符合规则的内容
1、返回值:是一个数组,如果捕获不到就是null
1、第一项是最大的捕获内容
2、数组的后几项是分组捕获的内容
3、index是第一次捕获位置的索引
4、input是原字符串
1.正则捕获的懒惰性
利用正则捕获时只能捕获到第一次出现符合规则的内容,如果想取消正则的懒惰性,就加修饰符g
// 正则身上有一个lastIndex属性,他记录的是正则捕获开始的位置的索引,如果正则不加g,那不管捕获多少次lastIndex始终是0,所以每一次捕获到的都是第一次负责规则的内容(这就是正则的懒惰性)
let str = "asd123as456as789d";
let reg = /\d{3}/;
// console.log(reg.lastIndex); // 0
// console.log(reg.exec(str)); // '123'
// console.log(reg.lastIndex); // 0
// console.log(reg.exec(str)); // '123'
// console.log(reg.lastIndex); // 0
// console.log(reg.exec(str)); // '123'
// 如果正则加上g,那每一次捕获之后正则的lastIndex属性就会记录下一次开始捕获的位置的索引,当下一次再次捕获的时候就会在lastIndex的位置继续捕获(这就是取消正则的懒惰性)
reg = /\d{3}/g;
// console.log(reg.lastIndex) // 0
// console.log(reg.exec(str)) // '123'
// console.log(reg.lastIndex) // 6
// console.log(reg.exec(str)) // '456'
// console.log(reg.lastIndex) // 11
// console.log(reg.exec(str)) // '789'
// console.log(reg.lastIndex) // 16
// console.log(reg.exec(str)) // null
// console.log(reg.lastIndex) // 0
// console.log(reg.exec(str)) // 跟第一次一样了 '123'
2.封装一个捕获全部内容的方法(不包含分组捕获)
let str = "asd123as456as789d";
let reg = /\d{3}/g;
// 正则身上有一个global属性,如果当前正则没有修饰符g,那他的值就是false,反之就是true
function myExec(str){
// 如果正则不加g,那正则的私有属性global的值就是false,
// 如果global的值就是false那就把他捕获一次直接return出去就好了
if(!this.global){
return this.exec(str);
};
let ary = []; // 用来存放每一次捕获到的内容
let res = this.exec(str) // 先捕获第一次
while(res){
// 每捕获一次就往ary里push一次捕获到的内容,直到捕获到null为止
ary.push(res[0])
// 然后在继续捕获
res = this.exec(str) // 继续进行捕获
}
// 如果正则第一次就捕获不到,while就不会执行,那ary是空数组,直接给他return null就好了
return ary.length === 0?null:ary;
};
// RegExp.prototype.myExec = myExec;
// console.log(reg.myExec(str)); // ['123','456','789']
3.字符串的match方法
String.prototype.match() match是字符串上的一个方法,用来捕获负责符合规则的内容
let str = "asd123as456as789d";
let reg = /\d{3}/g;
console.log(str.match(reg)); // ['123','456','789'] 和咱们刚才封装的方法实现的功能是一样的
// ---------------------------------------------------------------------------------------
// match的其他特点:
let str = "{0}年{1}月{2}日";
let reg = /\{(\d+)\}/;
//=>如果正则不设置g只会捕获一次,exec和match获取的结果一致
console.log(reg.exec(str)); // ["{0}", "0", index: 0, input: "{0}年{1}月{2}日", groups: undefined]
console.log(str.match(reg)); // ["{0}", "0", index: 0, input: "{0}年{1}月{2}日", groups: undefined]
//=>如果正则加上g,在多次捕获的情况下match只能把大正则匹配的内容获取到,小分组匹配的信息无法获取
reg = /\{(\d+)\}/;
console.log(str.match(reg)); // ["{0}", "{1}", "{2}"]
4.字符串的matchAll方法
String.prototype.matchAll() match是字符串上的一个方法,他的返回值是一个迭代器,迭代器里的每一项是每一次捕获到的所有内容(大捕获和分组捕获) 【迭代器可以用for of遍历】
let str = "{0}年{1}月{2}日";
let reg = /\{(\d+)\}/g;
console.log(str.matchAll(reg)); // RegExpStringIterator {} 迭代器
for (var ss of str.matchAll(reg)) {
console.log(ss);
// ["{0}", "0", index: 0, input: "{0}年{1}月{2}日", groups: undefined]
// ["{1}", "1", index: 4, input: "{0}年{1}月{2}日", groups: undefined]
// ["{2}", "2", index: 8, input: "{0}年{1}月{2}日", groups: undefined]
}
// 去遍历当前的迭代器可以得到每一次捕获的所有内容
5.封装一个捕获全部内容的方法(包含分组捕获)
//=>封装一个既能获取到大捕获的内容也能获取到小捕获内容的方法
// 利用matchAll进行封装
function execAll(str) {
// 如果正则不加g,那正则实例身上的私有属性global就是false,反之就是true
if (!this.global) {
// 直接给他捕获一次return 出去
return this.exec(str)
}
let big = [], // 创建一个大数组用来存储全局捕获的内容
small = []; // 创建一个小数组用来存储分组捕获的内容
for (var ss of str.matchAll(reg)) {
// console.log(ss); // 每一次捕获的内容
let [max,min] = ss;
big.push(max);
small.push(min);
}
return big.length === 0 ? null : { big, small }
// 把捕获到的内容return出去
// console.log(res)
}
RegExp.prototype.execAll = execAll;
console.log(reg.execAll(str));
//-----------------------------------------------------------------------------------------
// 利用原生进行封装
function execAll(str) {
// 如果正则不加g,那正则实例身上的私有属性global就是false,反之就是true
if (!this.global) {
// 直接给他捕获一次return 出去
return this.exec(str)
}
let big = [], // 创建一个大数组用来存储全局捕获的内容
small = [], // 创建一个小数组用来存储分组捕获的内容
res = this.exec(str); // 创建一个变量,用来存储每一次捕获的内容
while (res) {
let [max,min] = res;
big.push(max);
small.push(min);
// 重复执行正则捕获这个动作,直到获取的值为null为止
res = this.exec(str);
}
return big.length === 0 ? null : { big, small }
// 把捕获到的内容return出去
// console.log(res)
}
RegExp.prototype.execAll = execAll;
console.log(reg.execAll(str));
6.正则的贪婪性
正则捕获的贪婪性:默认情况下,正则捕获的时候,是按照当前正则所匹配的最长结果来获取的 在量词元字符后面设置?来取消正则的贪婪性
// 正则的贪婪性
let str = '2019';
let reg = /\d+/g;
console.log(str.match(reg)); // ['2019']
//=>在量词元字符后面设置?来取消捕获时候的贪婪性(按照正则匹配的最短结果来获取)
reg = /\d+?/g;
console.log(str.match(reg)); // ["2", "0", "1", "9"]
7.正则的replace方法
RegExp.prototype.replace() replace 是字符串中实现替换的方法(一般都是伴随正则一起使用的) replace 方法的传参是多样的
- ‘old’,’new’
- reg,’new’
- reg,function
let str = "zhufeng@2019|zhufeng@2020";
//=>需求:把"zhufeng"替换成"珠峰"
str = str.replace("zhufeng","珠峰").replace("zhufeng","珠峰");
console.log(str); // "珠峰@2019|珠峰@2020";
//---------------------使用正则会变得简单一点
str = str.replace(/zhufeng/g, "珠峰");
console.log(str); // "珠峰@2019|珠峰@2020"
//=>需求:把"zhufeng"替换为"zhufengpeixun"
let str1 = "zhufeng@2019|zhufeng@2020";
// str1=str1.replace("zhufeng","zhufengpeixun").replace("zhufeng","zhufengpeixun"); //"zhufengpeixunpeixun@2019|zhufeng@2020" 每一次替换都是从字符串第一个位置开始找的
//==>----------基于正则g实现
// str = str.replace(/zhufeng/g, "zhufengpeixun");
// console.log(str); // "zhufengpeixun@2019|zhufengpeixun@2020"
其实replace配合正则使用最牛的不是这些,而是在加上函数去使用……
String.prototype.replace(reg,function)
// 案例:把时间字符串进行处理
let time = "2019-08-13";
//=>需求:将time变为"2019年08月13日"
let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/g;
time = time.replace(reg,function(...ary){
//1.首先拿reg和time进行匹配捕获,能匹配到几次就会把回调函数执行几次
//2.回调函数不仅会执行,而且replace还给当前回调函数传递了实参信息(实参和exec捕获的内容一致的信息:大正则匹配的内容,小分组匹配的信息....)
//3.当前reaplce会返回一个大字符串,回调函数每一次return的结果会把大字符串里捕获到的值进行替换
// console.log(ary);
let [,$1,$2,$3] = ary; // 数组的解构
$2.length<2?$2="0"+$2:null; // 补零
$3.length<2?$3="0"+$3:null; // 补零
return $1+'年' + $2+'月'+ $3 + '日';
});
单词首字母大写
// 单词首字母大写
let str = "good good study,day day up!";
let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g;
//=>函数被执行了六次,每一次函数执行时接收到的实参是当前正则捕获到的信息
//=>每一次捕获到的信息:["good","g"] ["good","g"] ["study","s"]...
str = str.replace(reg,(...arg)=>{
let [content,$1]=arg;
$1=$1.toUpperCase(); // 把首字母转大写
content=content.substring(1); // 截取出来首字母之后的字符
return $1+content; // 把转化之后的大写字符和后边的字符拼接起来进行返回
});
console.log(str); //=>"Good Good Study,Day Day Up!"
意外惊喜(没有什么卵用,看看就好)
let str = 's3a3df4b4gh5c5jkl';
// 给正则加上g,其实利用匹配也可以实现捕获,每匹配一次,正则就会把当前捕获到的内容挂载到正则类上
// 这玩意用处不是很大
let reg = /(\d)([a-z])(\d)/g;
console.log(reg.test(str)); // true
console.dir(RegExp); // 正则类身上的$可以代表捕获到的分组内容 (第一次捕获到的内容)
console.log(reg.test(str)); // true
console.dir(RegExp); // 第二次捕获到的内容
七、queryUrlParams方法封装
let url = 'http://www.baidu.com?name=erYa&age=18#index';
function queryUrlParams() {
let reg = /([^?=&#]+)=([^?=&#]+)/g;
let obj = {}; // 创建一个空对象,用来存储一会处理的键值对
this.replace(reg, (content, key, value) => {
// content是每一次全局捕获的内容,
// key是每一次第一个分组捕获的内容(作为属性名)
// value是每一次第二个分组捕获的内容(作为属性值)
obj[key] = value;
});
// 把url里的参数捕获出来,以键值对的形式赋值给obj对象
this.replace(/#([^?=&#]+)/, (content, value) => {
// value是每一次分组捕获的内容(作为属性值)
obj['hash'] = value;
})
// 把url里的hash值捕获出来,以键值对的格式赋值给obj对象
return obj;
// 最后把obj对象return 出去
}
String.prototype.queryUrlParams = queryUrlParams;
console.log(url.queryUrlParams()); // {name: "erYa", age: "18", hash: "index"}
八、timeFormat方法封装
let time = '2019-12-3 12:12:3';
// 想要得到这样的结果 ==>'2019年12月03日 12时10分03秒'
function formatTime(template = '{0}年{1}月{2}日 {3}时{4}分{5}秒') {
let timeAry = this.match(/\d+/g);// 把字符串里的年月日时分秒都拿到,以数组的格式进行展示 ["2019", "12", "3", "12", "10", "3"]
// let template = '{0}年{1}月{2}日 {3}时{4}分{5}秒';// 编写一个模板,一会用来进行替换
template = template.replace(/\{(\d)\}/g, function (content, index) {
// index是每一次分组捕获的内容
// console.log(content, index)
// console.log(timeAry[index])
let time = timeAry[index] || "00"; // 如果index获取不到对应的值,那就默认赋值为 "00"
time.length < 2 ? time = "0" + time : null;
// 如果获取的时间不足十位就补零
return time;
})
return template
console.log(timeAry)
}
String.prototype.formatTime = formatTime;
console.log(time.formatTime('{1}~{2} {3}:{4}'))