正则表达式初体验!

前言

  • 细阅此文章大概需要 30分钟\color{red}{30分钟}左右
  • 本篇中详细讲述\color{red}{详细讲述}了:
    1. 正则表达式是什么
    2. 正则表达式的处理【匹配】【捕获】
    3. 创建正则
    4. 元字符
    5. 修饰符
    6. 小括号的作用
    7. 中括号的作用
    8. 问号的作用
    9. 正则匹配[test]
    10. 正则捕获的多种思路
    11. 练习题:单词首字母大写
    12. 练习题:日期字符串格式化
    13. 处理并封装一个格式化时间字符串的万能方法
    14. 获取URL地址中的问号传参信息&HASH信息
  • 如果有任何问题都可以留言给我,我看到了就会回复,如果我解决不了也可以一起探讨、学习。如果认为有任何错误都还请您不吝赐教,帮我指正,在下万分感谢。希望今后能和大家共同学习、进步。
  • 下一篇会尽快更新,已经写好的文章也会在今后随着理解加深或者加入一些图解而断断续续的进行修改。
  • 如果觉得这篇文章对您有帮助,还请点个赞支持一下,谢谢大家!

正则表达式

  • 正则表达式就是一个用来处理字符串的规则
  • 正则表达式就是 元字符修饰符组成 的规则

正则表达式的处理

  • 只能处理字符串
    • 【匹配】 验证字符串是否符合这个规则
    • 【捕获】 把字符串中符合规则的信息获取的

创建正则

字面量方式 let reg1 = /\d+/img;

  • 字面量方式,两个斜杠包起来的,都是它的元字符
  • 最后一个斜杠后面放的是修饰符,

构造函数方式 let reg2 = new Regexp('\\d+');

  • 构造函数方式,传递两个字符串,
  • 第一个字符串中放的是元字符
    • 当中是两个斜杠的原因,是因为传递的是俩个字符串,字符串中一个右斜杠没有任何意义,所以每次使用需要写两个,其中一个右斜杠是为了转义 【保证语法准确性】
  • 第二个字符串中放的是修饰符

元字符

量词元字符 【让左侧的某元字符出现多少次】

  • * 零到多次
  • + 一到多次
  • ? 零次或者一次
  • {n} 出现n次
  • {n,} 出现n到多次
  • {n,m} 出现n到m次

特殊元字符 【有特殊意义的元字符】

  • \ 转义字符(普通->特殊->普通)
  • . 除\n(换行符)以外的任意字符
  • ^ 以哪一个元字符作为开始
  • $ 以哪一个元字符作为结束
  • \n 换行符
  • \d 0~9之间的一个数字
  • \D 非【0~9之间的数字】的字符 (大写和小写的意思是相反的)
  • \w 数字、字母、下划线中的任意一个字符
  • \s 一个空白字符(包含空格、制表符、换页符等)
  • \t 一个制表符(一个TAB键:四个空格)
  • \b 匹配一个单词的边界
  • x|y x或者y中的一个字符 【使用时一般的伴随着小括号分组,可以改变默认的优先级】
  • [xyz] x或者y或者z中的一个字符
    • 中括号具有消磁的作用,在中括号中出现的元字符都是本身的含义
    • 但是像是\d之类的元字符,算是一个元字符,本身的意思就是0-9之间的一个数字
    • 中括号中出现的多位数代表着啥或啥,并不是多位数
  • [^xy] 除了x/y以外的任意字符
  • [a-z] 指定a-z这个范围中的任意字符 [0-9a-zA-Z_]===\w [0-9] === \d
  • [^a-z] 上一个的取反“非”
  • () 正则中的分组符号
    • 改变优先级
    • 分组捕获
  • (?:) 只匹配不捕获
  • (?=) 正向预查
  • (?!) 负向预查

普通元字符

  • \xxx\

修饰符

  • i =>ignoreCase 忽略单词大小写匹配
  • m =>multiline 可以进行多行匹配
  • g =>global 全局匹配

小括号的作用

  1. 【优先级】 :可以改变默认的优先级
  2. 【分组捕获】 :第一次捕获的时候,除了把大正则匹配的信息捕获到,而且还可以单独把每一个小分组匹配的信息捕获到

中括号的作用

  1. 【分组引用】\1或者\2…. 代表 复刻 出和第一个/第二个…分组一模一样的内容
    let reg = /^[a-zA-Z\d][a-zA-Z\d]\2\1$/; // ABBA
复制代码

问号在正则中的多种作用

  • 当?左边是非量词元字符 :它本身就是量词元字符,让左侧内容出现0~1次 /\w?/
  • 当?左边是量词元字符 :他的目的是取消捕获时的贪婪性 /\d?/g
  • (?:):当前分组只匹配不捕获
  • (?=):正向预查(断言),只有符合条件才可以
  • (?!):负向预查(断言),只有不符合条件才可以
    let reg = /^[a-zA-Z\d]{6,16}$/; // 密码可以由6-16位小写字母、大写字母、数字组成【简单】
    // 复杂的密码处理:必须同时包含大小写字母和数字
    // 负向预查版本
    let reg = /^(?!\d+$)(?![a-z]+$)(?![A-Z]+$)(?![a-z\d]+$)(?![A-Z\d]+$)(?![a-zA-Z]+$)[a-zA-Z\d]{6,16}$/;
    // 正向预查版本
    let reg = /^(?=[a-zA-Z]*\d+[a-zA-Z]*)(?=[A-Z\d]*[a-z]*[A-Z\d]*)(?=[])/ 
复制代码

正则匹配:正则对象.test(字符串)

    // 正则匹配:正则对象.test(字符串)
// ===========================================================
    // ^和$符:以什么开头,以什么结尾,都加则为只能是这个东西
    let reg = /\d+/; // 只要字符串中包含一到多个数字
    console.log(reg.test('hahahahaha')) // false
    console.log(reg.test('hahahahaha2021')) // true

    reg = /^\d+/; // 字符串必须要以一到多个数字开头
    console.log(reg.test('hahahahaha')) // false
    console.log(reg.test('hahahahaha2021')) // false
    console.log(reg.test('2021hahahahaha')) // true

    reg = /\d+$/; // 字符串必须要以一到多个数字结束
    console.log(reg.test('hahahahaha')) // false
    console.log(reg.test('hahahahaha2021')) // true
    console.log(reg.test('2021hahahahaha')) // false
    
    reg = /^\d+$/; // 字符串[只能]是一到多个数字
    console.log(reg.test('hahahahaha')) // false
    console.log(reg.test('hahahahaha2021')) // false
    console.log(reg.test('2021hahahahaha')) // false
    console.log(reg.test('2020hahahahaha2021')) // false
    console.log(reg.test('20202021')) // true

    reg = /^\d$/; // 字符串只能是一个数字
    console.log(reg.test('1')) // true

    reg = /^2.6$/;
    console.log(reg.test('2.6')); // true
    console.log(reg.test('2+6')); // true
    console.log(reg.test('286')); // true
    console.log(reg.test('26')); // false 【至少三位才可以,第二位是任何东西都可以】
    console.log(reg.test(`2
    6`)); // false 【第二位不能是换行,因为第二位规则是.】


    reg = /^2\.6$/; // 转义字符可以将特殊字符变成普通字符,也可以把普通字符变为特殊字符
    console.log(reg.test('2.6')); // true // 此时只能是2.6
    console.log(reg.test('2+6')); // false
// ===========================================================
    // \ 转义字符
    reg = /^\\d$/; // 字符串只能是右斜杠和d
    console.log(reg.test('d')) // false、
    console.log(reg.test('\d')) // false 【因为字符串中右斜杠不能单独出现】
    console.log(reg.test('\\d')) // true 【字符串中右斜杠不能单独出现】
// ===========================================================

    // x|y 的使用一定伴随着小括号
    let reg1 = /^2|6$/;
    console.log(reg1.test('2')); // true
    console.log(reg1.test('3')); // true
    console.log(reg1.test('23'));// true
    console.log(reg1.test('4')); // true
    let reg2 = /^(2|6)$/;
    console.log(reg2.test('2')); // true
    console.log(reg2.test('3')); // true
    console.log(reg2.test('23'));// false
    console.log(reg2.test('4')); // false
    let reg3 = /^14|56$/;
    console.log(reg3.test('14')); // true
    console.log(reg3.test('56')); // true
    console.log(reg3.test('146'));// true
    console.log(reg3.test('145')); // true
    console.log(reg3.test('156')); // true
    console.log(reg3.test('1456')); // true
    console.log(reg3.test('16')); // false

    let reg4 = /^(14|56)$/;
    console.log(reg4.test('14')); // true
    console.log(reg4.test('56')); // true
    console.log(reg4.test('146'));// false
    console.log(reg4.test('145')); // false
    console.log(reg4.test('156')); // false
    console.log(reg4.test('1456')); // false
    console.log(reg4.test('16')); // false



    // [xyz] [^xy] [a-z] [^a-z]
    let reg5 = /^[.+]$/
    console.log(reg5.test('a')); //false
    console.log(reg5.test('aaaa')); //false
    console.log(reg5.test('.')); //true
    console.log(reg5.test('+')); //true 

    // \d本身是一个元字符,本身的意思就是0-9之间的一个数字
    let reg6 = /^[\d]$/;
    console.log(reg6.test('9')); //true
    console.log(reg6.test('d')); //false 

    let reg7 = /^[12-369]$/; // 中括号中出现的多位数代表着啥或啥,并不是多位数
    console.log(reg7.test('12')); // false
    console.log(reg7.test('369')); // false
    console.log(reg7.test('19'));// false
    console.log(reg7.test('25')); // false
    console.log(reg7.test('40')); // false
    console.log(reg7.test('1')); // true
    console.log(reg7.test('2')); // true
    console.log(reg7.test('3')); // true
    console.log(reg7.test('6')); // true
    console.log(reg7.test('9')); // true
    console.log(reg7.test('4')); // false

// ===========================================================
    // 需求:匹配一个年龄 18-65
    // 思路:18-19 20-59 60-65分段
    let ageReg = /^((1[8-9])|([2-5]\d)|(6[0-5]))$/

    // 需求:验证手机号码
    // 思路:十一位,以一开始,后面十位
    // 但是验证真实正则永远验证不了,所以轻验证,格式正确即可
    let phoneReg = /^1\d{10}$/

// ===========================================================
    // 需求:验证密码
    // 思路:6-16位,数字和字母组合,必须包含大小写及数字
    let passReg = /^[0-9a-zA-Z]{6,16}$/;

    // 需求:验证真实姓名
    // 思路:中文汉字,至少两位,可以包含点·,
    /* 中文码范围 */`[\u4E00-\u9FA5]`
    let chineseNameReg = /^[\u4E00-\u9FA5]{2,10}(·[\u4E00-\u9FA5]{1,10})?$/;
// ===========================================================
    // 需求:验证邮箱
    // 思路:以@为分割线,拆为两部分
    // 数字字母下划线是标准命名,可以出现中杠和点,但是不能作为开始,而且不能连续出现【例如:li__.shuo这样是不对的】
    /* 中文码范围 */`[\u4E00-\u9FA5]`
    let emailReg = /^[\u4E00-\u9FA5]{2,10}(·[\u4E00-\u9FA5]{1,10})?$/;
    // 更完整的验证
    let emailReg2 = /^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/;
    // \w+((-\w+)|(\.\w+))*   数字、字母、下划线是标准的命名,可以出现中杠和点,但是不能作为开始,而且不能连续出现,例如:zhou--.xiao 这样是不对的
    // @
    // [A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+
    //    [A-Za-z0-9]+\.[A-Za-z0-9]+    qq.com  163.cn
    //    ((\.|-)[A-Za-z0-9]+)*   .com  -xxx    企业邮箱  zhangsan@tengxun.com.cn */
// ============================================================
复制代码

正则捕获:正则对象.exec(字符串)

  • 把字符串中符合正则匹配规则的内容捕获到
    • 若字符串中没有任何信息与正则相匹配,那么获取的结果就是null

正则捕获的特点

  1. 【’懒’】 ,执行一次或者多次捕获,只能把第一个匹配的信息捕获到 ,后面其他匹配的信息无法捕获到
    • 正则的原型对象上有一个 公共属性lastIndex,存着下次开始查找的索引,默认情况下不会修改其值,为0。 所以每一次捕获都是从第一个字符开始查找的,所以永远只能捕获第一个匹配的
    • 正则表达式中有一个修饰符g可设置两个值(global:false/true),默认为false ,这个修饰符是全局匹配 【每一次捕获完,会修改lastIndex值】
    • 【重要】 但如果使用加了修饰符g的正则规则,无论是test还是exec,都会修改lastIndex,所以如果连着使用,就会出现问题!!!!!
        let str = 'lsw123lsw'
            reg = /\d+/,
            reg1= /\d+/g;
        if(reg.test(str)){
            console.log(reg.exec(str));//123
        }
        if(reg1.test(str)){
            console.log(reg.exec(str));//null
        }
    复制代码
  2. 【正则捕获还具备贪婪性】
    • 正则每一次捕获都是按照最长的匹配内容获取
        let str = 'lsw2021',
            reg = /\d+/g,
            reg2 = /\d+?/g;
        console.log(str.match(reg));// --->['2021'] 
        console.log(str.match(reg2));// --->['2','0','2','1']
    
    复制代码

解决正则捕获懒的特点

【方法一】 修饰符g可以解决正则捕获的特点

    let str = 'lsw',
        str1 = 'lsw2020happy2021everyday'
        reg = /\d+/,
        reg1 = /\d+/g;
    console.log(reg.test(str));
    console.log(reg.exec(str)); // null
    // 一般在捕获前都先匹配一下,如果没有就不进行捕获
    reg.test(str)?console.log(reg.exec(str)):null;
    console.log(reg.exec(str1)); // ["2020", index: 3, input: "lsw2020happy2021", groups: undefined]
    // 第一项是正则捕获的信息,

    // 正则捕获的懒惰性
    console.log(reg1.lastIndex);//0
    console.log(reg1.exec(str1)); // ["2020", index: 3, input: "lsw2020happy2021", groups: undefined]
    console.log(reg1.lastIndex);//7
    console.log(reg1.exec(str1)); // ["2021", index: 3, input: "lsw2020happy2021", groups: undefined]
    console.log(reg1.lastIndex);//16
    console.log(reg1.exec(str1)); //null
    // 后面没有了,lastIndex就又重置为0
    console.log(reg1.lastIndex);//0
    console.log(reg1.exec(str1)); //null


复制代码
为了能【一次捕获到所有匹配的】,就在正则原型上写一个方法execAll
    RegExp.prototype.execAll = function execAll(str){
        // this===>reg   str===>字符串
        str+=''; //无论传进来是什么格式,先转为字符串
        // 正则捕获一定得设置global全局修饰符,不如只能取到第一个符合的
        // 没有设置g只捕获一次,每次都不会是null,就会变成死循环
        if(!this.global){
            return this.exec(str);
        }
        // 循环处理exec,直到捕获结果为null,把每次捕获的信息存储起来
        let arr = [],
            result;
            while(result = this.exec(str)){ // 这里的条件其实做了两件事 @1 正则捕获,拿到结果付给result @2 校验result是否为null,若是则循环结束
                // result中存储的是每一次捕获的结果【是个数组】
                arr.push(result[0]);
            }
            return arr;
    };
复制代码

【方法二】 字符串的match方法

  • String.prototype.match 和正则表达式相结合使用,意思是捕获到当前字符串中,所有符合正则规则的内容
  • 【match有本身的局限性(若设置了分组捕获同时又使用了g修饰符)】 :如果正则设置了g修饰符,则基于match捕获的信息只包含大正则匹配的,小分组单独匹配的获取不到;如果没有设置g,则和exec执行的结果是一样的
    let str = 'lsw2020happy2021everyday'
        reg = /\d+/,
        reg1 = /\d+/g;
    console.log(str.match(reg1)); // ["2020", "2021"]
    console.log(str.match(reg)); // ["2020", index: 3, input: "lsw2020happy2021", groups: undefined]
复制代码

【但是match有本身的局限性】eg.身份证的正则处理【拿到对应位数来得到对应的数据】

    let id = '210404199504182117';
    let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})(\d{2})(\d)(?:X|\d)$/g;
    let reg1 = /^(\d{6})(\d{4})(\d{2})(\d{2})(\d{2})(\d)(?:X|\d)$/;
    console.log(id.match(reg));// ["210404199504182117"]
    console.log(id.match(reg1));// ["210404199504182117", "210404", "1995", "04", "18", "21", "1", index: 0, input: "210404199504182117", groups: undefined]
复制代码

正则处理时间字符串

时间字符串的处理.png


【正则捕获方法2】用正则匹配来实现捕获

  • RegExp类上有个静态私有属性$
    • $& 表示本次test执行得到的匹配结果 RegExp.$&
    • $1-9 存储的是本次捕获得到的第一个到第九个小分组匹配信息 RegExp.$1~9
     let str = 'lsw123lsw'
            reg = /\d+/,
            reg1= /\d+/g,
            reg2 = /(\d)\d+(\d)/g;
    reg1.test(str);
    console.log( RegExp['$&']); // ====>'123'

    reg2.test(str);
    console.log( RegExp['$&']); // ====>'123'
    console.log( RegExp['$1']); // ====>'1'
    console.log( RegExp['$2']); // ====>'2'

复制代码

【正则捕获方法3】用字符串方法Repalce来实现捕获

  • repalce :执行一次只能替换一个匹配的内容
    let str = 'zhenbucuo2020zhenbucuo2021';
    str = str.replace('zhenbucuo','真不错'); // '真不错2020zhenbucuo2021'
    str = str.replace('zhenbucuo','真不错'); // '真不错2020真不错2021'
    console.log(str);
复制代码
  • 但对于某些需求执行多次replace也是实现不了的
    1. 在replace的使用中,通常都是配合着正则使用的:找到str中所有和正则匹配的内容,把所有内容依次替换成’想要的内容’,但是需要设置修饰符g【改变lastIndex大的值】
        let str = 'zbc2020zbc2021';
        str = str.replace('zbc','zbcZzsl'); // 'zbcZzslZzsl2020zbcZzsl2021'
        str = str.replace(/zbc/g,'zbcZzsl'); // 'zbcZzsl2020zbcZzsl2021'
        console.log(str);
    复制代码
    1. 如果replace第二个参数是一个回调函数。第一个参数是一个正则,
      • 拿正则和字符串进行匹配,匹配几次,就会把回调函数执行几次
      • 不仅把回调函数执行,而且还给回调函数传递了实参信息:
        • 把每一次正则匹配的结果 【大正则及小分组正则】 传递给回调函数【实现捕获的一步】
        • 回调函数返回什么,就相当于把当前的正则匹配的部分替换成什么
        let str = 'zbc2020zbc2021';
        // 正则匹配了两次,就执行两次回调函数
        str = str.replace(/\d+/g,function(val){
            console.log(val);// @1 2020 @2 2021
            // 回调函数返回什么,就相当于把当前的正则匹配的部分替换成什么
            return '@'+val;
        }); 
        console.log(str); // zbc@2020zbc@2021
    复制代码
        // 需求:把字符串当中{数字}替换为数组中指定项内容,例如{2}替换为arr[2]的内容
        let str = '我的名字是{0},我是一个{1}孩,我今年{2}岁,我喜欢{3}',
            arr = ['lsw','男','18','打代码'];
        // 思路:先拿到大括号加数字,然后取出数字作为索引到数组中取出对应的内容
        // 大正则拿到{数字},小分组取到 数字
        str = str.replace(/\{(\d+)\}/g,function(val,$1){
            // val----->每一次大正则匹配的信息 {数字}
            // $1----->每一次第一个小分组匹配的信息 数字
            console.log(arguments); 
            //  ["{0}", "0", 5, "我的名字是{0},我是一个{1}孩,我今年{2}岁,我喜欢{3}", callee: ƒ, Symbol(Symbol.iterator): ƒ]
            console.log(val);// @1 {0} @2 {1} @3 {2} @4 {3}
            // 回调函数返回什么,就相当于把当前的正则匹配的部分替换成什么
            return arr[$1];// @1 lsw @2 男 @3 18 @4 打代码
        }); 
        console.log(str); // '我的名字是lsw,我是一个男孩,我今年18岁,我喜欢打代码'
    复制代码

  • 练习题:把单词首字母大写
        let str = 'good good study! day day up!',
            reg = /\b([a-zA-Z])([a-zA-Z]*)\b/g;
        str = str.replace(reg,function(_,$1,$2){
            // val ----->每一次大正则匹配的信息 ------ 单词【其实用不到,可以不设置形参接收,用下划线占位也可以】
            // $1 ----->每一次第一个小分组匹配的信息 ------ 单词首字母
            // $2 ----->每一次第一个小分组匹配的信息 ------ 单词剩余字母
    
            console.log(arguments); 
            //  ["good", "g", "ood", 0, "good good study! day day up!", callee: ƒ, Symbol(Symbol.iterator): ƒ]
            //console.log(val);// @1 good @2 good @3 study @4 day @5 day @6 up
            console.log($1);// @1 g @2 g @3 s @4 d @5 d @6 u
            console.log($2);// @1 ood @2 ood @3 tudy @4 ay @5 ay @6 p
    
            // 回调函数返回什么,就相当于把当前的正则匹配的部分替换成什么
            return $1.toUpperCase()+$2;// @1 Good @2 Good @3 Study @4 Day @5 Day @6 Up
        }); 
    复制代码

  • 练习题:日期字符串格式化
        // 模板编译来实现
        let str = '2021-7-6 15:51:8',
            arr = str.match(/\d+/g); //先获取到所有时间数字
            reg = /\{(\d+)\}/g,
            template = '{0}年{1}月{2}日 {3}时{4}分{5}秒';//编写模板
    
        template = template.replace(reg,function($1,$2){
            // val----->每一次大正则匹配的信息 {数字}
            // $1----->每一次第一个小分组匹配的信息 数字
            console.log($1);// @1 {0} @2 {1} @3 {2} @4 {3} @5 {4} @6 {5} 
            console.log($2);// @1 0 @2 1 @3 2 @4 3 @5 4 @6 5
            let val = arr[$2] || '00'; // 拿数据或00
            if(val.length<2)val = '0'+val; // 只有一位补零
            // 回调函数返回什么,就相当于把当前的正则匹配的部分替换成什么
            return val;// @1 2021 @2 07 @3 06 @4 15 @5 51 @6 08
        }); 
    复制代码

字符串处理相关的方法封装

封装一个时间字符串模板
    /* 
     *
     * formatTime:时间字符串格式化
     *  @params
     *      template[string]:时间字符串模板{0、1、2、3、4、5:分别代表年月日时分秒}
     *  @return
     *      [string]:最后想要的时间格式字符串
     * 
    */
    String.prototype.formatTime = function formatTime(template) {
        template = template || '{0}年{1}月{2}日 {3}时{4}分{5}秒';
        let arr = this.match(/\d+/g);
        return template.replace(/\{(\d+)\}/g, (_, $1) => {
            let val = arr[$1] || '00';
            if (val.length < 2) val = '0' + val;
            return val;
        });
    };
复制代码
获取URL地址中的:问号传参信息&HASH信息
    /* 
     *
     * queryURLParams: 地址栏参数信息的解析
     *  @params
     *  @return
     *      [object]:包含问号参数信息及哈希值的对象
     * 
    */
    String.prototype.queryURLParams = function queryURLParams(){
        let obj = {};
        this.replace(/#([^?=#&]+)/g,(_,$1)=>obj['HASH']=$1);
        this.replace(/([^?=#&]+)=([^?=#&]+)/g,(_,$1,$2)=>obj[$1]=$2);
        return obj;
    }
    
    let url1 = 'http://www.hahahahah.com/?name=LSW&age=18&sex=male#CEO';
    console.log(url1.queryURLParams());

复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享