一、认识正则
- (RegExp)Regular Expression:是js的一个内置类
- 正则,就是一个规则,可以检验某个字符串是否符合这个规则(test),也可以把字符串中符合某个规则的字符捕获到(exec 、match…)
- 主要用来处理字符串
let reg = /\d+/; // 0-9之间的数字出现1到多次let str = 'd12fgh';console.log(reg.test(str)); // trueconsole.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之外的任意一个字符():分组(?:):只匹配,不捕获(?=):正向预查(?!):负向预查普通元字符:bnd...
修饰符:就是对正则起到修饰作用的 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到9let 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')) // trueconsole.log(reg.test('fonod')) // true
5.[] 中不允许出现多位数
let reg = /^[21-68]$/; // 2 或者 1-6 或者8 之间取一个数console.log(reg.test('21')); // falseconsole.log(reg.test('68')); // falseconsole.log(reg.test('2')); // trueconsole.log(reg.test('5')); // trueconsole.log(reg.test('8')); // truelet 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.41、+-可有可无,而且在开头, [+-]?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('爱新觉罗·溥仪')) // trueconsole.log(reg.test('阿诺德·施瓦辛格·溥仪')) // trueconsole.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、返回值:是一个数组,如果捕获不到就是null1、第一项是最大的捕获内容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,反之就是truefunction 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,反之就是trueif (!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,反之就是trueif (!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)); // trueconsole.dir(RegExp); // 正则类身上的$可以代表捕获到的分组内容 (第一次捕获到的内容)console.log(reg.test(str)); // trueconsole.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 templateconsole.log(timeAry)}String.prototype.formatTime = formatTime;console.log(time.formatTime('{1}~{2} {3}:{4}'))
