写给自己看Javascript正则

139 阅读7分钟

正则表达式

下面部分参考了:老姚后盾人mdxy-dxy的博客

正则表达式的强大之处就可以模糊描述某一段或者某一类字符串。其实生活中,我们经常在用“正则表达式”。比如说我们想去广州越秀山,不认识路,跟路人说,有五羊石像的景区怎么走;这个描述其实就是一种正则表达,我们在用另外一种方式描述我们想找的东西

  1. 两种模糊查询

    (1) 横向模糊查询

    横向模糊指的是,一个正则可匹配的字符串的局部的长度不是固定的,可以是多种情况的

    比如说: 在一条村里面找出有3到5片田的人

    则匹配模式就是 /田{3,5}/

    (2) 纵向模糊指的是,一个正则匹配的字符串,具体到某一位字符时,它可以不是某个确定的字符,可以有多种可能

    比如说:在一个班里面找出有男朋友或者有女朋友的人

    则匹配模式就是 /[(有男朋友) (有女朋友) ]/

  2. 字符组

    虽叫字符组(字符类),但只是其中一个字符

    例如 [abc],表示匹配一个字符,它可以是 "a"、"b"、"c" 之一

    (1) 范围表示法

    [123456abcdefGHIJKLM],可以写成 [1-6a-fG-M]。用连字符 - 来省略和简写

    注意:因为连字符有特殊用途 ,要使用时需要转义

    (2) 排除字符组

    排除字符组(反义字符组)的概念。例如 [^abc],表示是一个除 "a"、"b"、"c"之外的任意一个字 符。字符组的第一位放 ^(脱字符),表示求反的概念

    (3) 常见的简写形式

    如果要匹配任意字符怎么办?可以使用 [\d\D]、[\w\W]、[\s\S] 和 [^] 中任何的一个

  3. 量词

    量词也称重复。掌握 {m,n} 的准确含义后,只需要记住一些简写形式

  4. 贪婪匹配

    正则默认是贪婪的,它会尽可能多的匹配

    (function () {
        var regex = /\d{2,5}/g;
        var string = "123 1234 12345 123456";
        console.log(string.match(regex));
        // => ["123", "1234", "12345", "12345"]
    })();
    
    (function () {
        let people = "你老板问你想要多少钱/月,你说想要10000000000";
        console.log(people.match(/你老板问你想要多少钱\/月,你说想要10+/g)); 
        //["你老板问你想要多少钱/月,你说想要10000000000"]
    })();
    
  5. 惰性匹配

    在量词后面添加? 表示惰性匹配,在准确的情况下会匹配最少

    var regex = /\d{2,5}?/g;
    var string = "123 1234 12345 123456";
    console.log( string.match(regex) );
    // => ["12", "12", "34", "12", "34", "12", "34", "56"]
    
    (function () {
        let people = "你老板说想想就好了,我只能给你月薪10000000000";
        console.log(people.match(/你老板说想想就好了,我只能给你月薪10{3,}?/g));
        //["你老板说想想就好了,我只能给你月薪1000"]
    })();
    

    通过在量词后面加个问号就能实现惰性匹配,因此所有惰性匹配情形如下:

  6. 多选分支

    一个模式可以实现横向和纵向模糊匹配。而多选分支可以支持多个子模式任选其一。 具体形式如下:(p1|p2|p3),其中 p1、p2 和 p3 是子模式,用 |(管道符)分隔,表示其中任何之一

    var regex = /good|nice/g;
    var string = "good idea, nice try.";
    console.log( string.match(regex) );
    // => ["good", "nice"]
    

    分支结构也是惰性的,第一个分支可以匹配先给第一个匹配,不能匹配则给第二分支匹配

    (function () {
        var regex = /good|goodbye/g;
        var str = "goodbye goodbye xiaoming";
        console.log(str.match(regex));//["good","good"]
    })();
    
    (function () {
        var regex = /goodbye|good/g;
        var string = "goodbye goodbye xiaoming";
        console.log(string.match(regex)); //["goodbye", "goodbye"]
    })();
    
    (function () {
        var regex = /goodbye|good/g;
        var string = "goodidea goodbye xiaoming";
        console.log(string.match(regex)); //["good", "goodbye"]
    })();
    
  7. 匹配 16 进制颜色值

    #ffbbad #Fc01DF #FFF #ffE

    var regex = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g;
    
  8. 匹配时间

    23:09 02:07

    分析: 共 4 位数字,第一位数字可以为 [0-2]。 当第 1 位为 "2" 时,第 2 位可以为 [0-3],其他情况时,第 2 位为 [0-9]。 第 3 位数字为 [0-5],第4位为 [0-9]。

    var regex = /^([01][0-9]|[2][0-3]):[0-5][0-9]$/;
    console.log( regex.test("23:59") );
    console.log( regex.test("02:07") );
    

    如果也要求匹配 "7:9",也就是说时分前面的 "0" 可以省略 则为

  9. 匹配日期

    2017-06-10

    年,四位数字即可,可用 [0-9]{4}。 月,共 12 个月,分两种情况 "01"、"02"、…、"09" 和 "10"、"11"、"12",可用 (0[1-9]|1[0-2])。 日,最大 31 天,可用 (0[1-9]|[12][0-9] | 3 [01])

  10. window 操作系统文件路径

F:\study\javascript\regex\regular expression.pdf F:\study\javascript\regex
F:\study\javascript F:\

(1) 匹配 "F:\\",需要使用 [a-zA-Z]:\\\\,其中盘符不区分大小写,注意 \ 字符需要转义
(2) 文件名或者文件夹名,不能包含一些特殊字符,此时我们需要排除字符组 \[^\\\\:*<>|"? \r \n/ ] 来表示合法字符。
(3) 另外它们的名字不能为空名,至少有一个字符,也就是要使用量词 +。因此匹配 文件夹 \\,可用\[^\\\\:*<>|"?\\r\\n/]+\\\\
(4) 另外 文件夹\\,可以出现任意次也就是 (\[^\\\\:* <>|"?\r\n/]+)?  

  1. 匹配id

    var regex = /id="[^"]*"/
    var string = '<div id="container" class="main"></div>';
    console.log(string.match(regex)[0]);
    // => id="container"
    
  2. 正则匹配位置

    正则表达式是匹配模式,要么匹配字符,要么匹配位置

    什么是位置呢? 比如,下图中箭头所指的地方:

    (1) ^(脱字符)匹配开头,在多行匹配中匹配行开头。 (2) $(美元符号)匹配结尾,在多行匹配中匹配行结尾。

    比如我们把字符串的开头和结尾用 "#" 替换(位置可以替换成字符的!):

    var result = "hello".replace(/^|$/g, '#');
    console.log(result);
    // => "#hello#"
    

    多行匹配模式(即有修饰符 m)时,二者是行的概念,这一点需要我们注意:

    var result = "I\nlove\njavascript".replace(/^|$/gm, '#');
    console.log(result);
    /*
    #I#
    #love#
    #javascript#
    */
    

    (3) \b 是单词边界,具体就是 \w(字母数字或者下划线 ) 与 \W 之间的位置,也包括 \w 与 ^ 之间的位置,和 \w 与 $ 之间的位置

    比如考察文件名 "[JS] Lesson_01.mp4" 中的 \b,如下:

    var result = "[JS] Lesson_01.mp4".replace(/\b/g, '#');
    console.log(result);
    // => "[#JS#] #Lesson_01#.#mp4#"
    

    (4) \B 就是 \b 的反面的意思,非单词边界 ,比如具体说来就是 \w 与 \w、 \W 与 \W、^ 与 \W,\W 与 $ 之间的位置

    var result = "[JS] Lesson_01.mp4".replace(/\B/g, '#');
    console.log(result);
    // => "#[J#S]# L#e#s#s#o#n#_#0#1.m#p#4"
    

    (5) 正向先行断言 (?=p),其中 p 是一个子模式,即 内容为p 的字符/段的 前面的位置

    var result = "hello".replace(/(?=l)/g, '#');
    console.log(result);
    // => "he#l#lo"
    

    (6) 负向先行断言 (?!p) 后面不能出现 p 指定的内容,即 不是内容为p 的字符/段的 前面的位置

    var result = "hello".replace(/(?!l)/g, '#');
    console.log(result);
    // => "#h#ell#o#"
    

    (7) 零宽后行断言 (?<=exp) 匹配 内容exp 后面的位置

    var result = "hello".replace(/(?<=l)/g, '#');
    console.log(result);
    // => hel#l#o
    

    (9) 零宽负向后行断言(?<!exp) 匹配 不是exp 后面的位置

    let hd = "hello";
    let reg = /(?<!l)/g;
    let result =  hd.replace(reg,"#");
    console.log(result)//#h#e#llo#
    

    < 表示在exp或者非epx的后面 的位置

  3. 数字的千位分隔符表示法

    比如把 "12345678",变成 "12,345,678"

    var regex = /(?!^)(?=(\d{3})+$)/g;//不是在开头的前面的位置 且 是在结尾前面 且 是至少出现一次三位数前面
    var result = "12345678".replace(regex, ',')
    console.log(result);
    // => "12,345,678"
    result = "123456789".replace(regex, ',');
    console.log(result);
    // => "123,456,789"
    
  4. 验证密码

    必须包含数字的原子组 (?=.*[0-9])

    (?=.*[0-9]) 表示该位置后面的字符匹配 .*[0-9],即,有任何多个任意字符,后面再跟个数字

    必须包含小写字母的原子组 (?=.*[a-z])

  5. 括号

    强调括号内的正则是一个整体,即提供子表达式

    /a+/ 匹配连续出现的 "a",而要匹配连续出现的 "ab" 时,需要使用 /(ab)+/

    (1) 分组引用

    以日期为例。假设格式是 yyyy-mm-dd 的,我们可以先写一个简单的正则

    var regex = /\d{4}-\d{2}-\d{2}/;
    

    然后再修改成括号版的:

    var regex = /(\d{4})-(\d{2})-(\d{2})/;
    

    对比这两个可视化图片,与前者相比,后者多了分组编号,如 Group #1。 正则引擎在匹配过程中,会给每一个分组都开辟一个空间,用来存储每一个分组匹配到的数据。 既然分组可以捕获数据,那么我们就可以使用它们

    (2) 提取数据 (没有完善,怎么提取)

    比如提取出年、月、日,可以这么做:

    var regex = /(\d{4})-(\d{2})-(\d{2})/;
    var string = "2017-06-12";
    console.log( string.match(regex) );
    // => ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"]
    
    var regex = /(\d{4})-(\d{2})-(\d{2})/;
    var string = "2017-06-12";
    regex.test(string); // 正则操作即可,例如
    //regex.exec(string);
    //string.match(regex);
    console.log(RegExp.$1); // "2017"
    console.log(RegExp.$2); // "06"
    console.log(RegExp.$3); // "12"
    

    (3) 替换

    比如,想把 yyyy-mm-dd 格式,替换成 mm/dd/yyyy

    var regex = /(\d{4})-(\d{2})-(\d{2})/;
    var string = "2017-06-12";
    var result = string.replace(regex, "$2/$3/$1");
    console.log(result);
    // => "06/12/2017"
    

    其中 replace 中的,第二个参数里用 11、2、$3 指代相应的分组。等价于如下的形式:

    var regex = /(\d{4})-(\d{2})-(\d{2})/;
    var string = "2017-06-12";
    var result = string.replace(regex, function () {
    return RegExp.$2 + "/" + RegExp.$3 + "/" + RegExp.$1;
    });
    console.log(result);
    // => "06/12/2017
    

    也等价于:

    var regex = /(\d{4})-(\d{2})-(\d{2})/;
    var string = "2017-06-12";
    var result = string.replace(regex, function (match, year, month, day) {
    return month + "/" + day + "/" + year;
    });
    console.log(result);
    // => "06/12/2017
    
  6. 反向引用

    正则本身里可以引用分组,但只能引用之前出现的分组,即反向引用

    比如要写一个正则支持匹配如下三种格式:

    2016-06-12 2016/06/12 2016.06.12

    假设我们想要求分割符前后一致怎么办?此时需要使用反向引用:

    var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
    var string1 = "2017-06-12";
    var string2 = "2017/06/12";
    var string3 = "2017.06.12";
    var string4 = "2016-06/12";
    console.log( regex.test(string1) ); // true
    console.log( regex.test(string2) ); // true
    console.log( regex.test(string3) ); // true
    console.log( regex.test(string4) ); // false
    

    注意里面的 \1,表示的引用之前的那个分组 (-|\/|\.),不管分组1匹配到什么(比如 -),\1 都匹配和分组1同样的具体某个字符

    (2) 括号嵌套

    var regex = /^((\d)(\d(\d)))\1\2\3\4$/;
    var string = "1231231233";
    console.log( regex.test(string) ); // true
    console.log( RegExp.$1 ); // 123
    console.log( RegExp.$2 ); // 1
    console.log( RegExp.$3 ); // 23
    console.log( RegExp.$4 ); // 3
    

    第一个是 \1,是第一个分组内容,那么看第一个开括号对应的分组是什么,是 "123"

    接下来的是 \2,找到第2个开括号,对应的分组,匹配的内容是 "1"

    接下来的是 \3,找到第3个开括号,对应的分组,匹配的内容是 "23"

    最后的是 \4,找到第3个开括号,对应的分组,匹配的内容是 "3"

    (3) 分组后面有量词

    分组后面有量词的话,分组最终捕获到的数据是最后一次的匹配

    var regex = /(\d)+/;
    var string = "12345";
    console.log( string.match(regex) );
    // => ["12345", "5", index: 0, input: "12345"]
    

    同理对于反向引用,也是这样的。测试如下

    var regex = /(\d)+ \1/;
    console.log( regex.test("12345 1") );
    // => false
    console.log( regex.test("12345 5") );
    // => true
    
  7. 非捕获括号

    使用括号,会捕获它们匹配到的数据,以便后续引用,因此也称它们是捕获型分组和捕获型分 组

    如果只想要括号最原始的功能,但不会引用它,即,既不在 API 里引用,也不在正则里反向引用。 此时可以使用非捕获括号 (?:p) 和 (?:p1|p2|p3)

    var regex = /(?:ab)+/g;
    var string = "ababa abbb ababab";
    console.log( string.match(regex) );
    // => ["abab", "ab", "ababab"]
    
  8. 操作符的优先级

    竖杠优先级最低

  9. 正则表达式的四种操作

    (1) 验证

    var regex = /\d/;
    var string = "abc123";
    console.log(regex.test(regex))//true
    

    (2) 切分

    var regex = /\D/;
    console.log("2017/06/26".split(regex));
    console.log("2017.06.26".split(regex));
    console.log("2017-06-26".split(regex));
    // => ["2017", "06", "26"]
    

    (3) 提取

    正则通常要使用分组引用(分组捕获)功能,还需要配合使用相关 API

    var regex = /^(\d{4})\D(\d{2})\D(\d{2})$/;
    var string = "2017-06-26";
    console.log( string.match(regex) );
    // =>["2017-06-26", "2017", "06", "26", index: 0, input: "2017-06-26"]
    

    使用excu全局匹配

    var string = "2017.06.27";
    var regex2 = /\b(\d+)\b/g;
    var result;
    //配合while循环使用(全局匹配常用)
    while ( result = regex2.exec(string) ) {
    console.log( result, regex2.lastIndex );
    }
    // => ["2017", "2017", index: 0, input: "2017.06.27"] 4
    // => ["06", "06", index: 5, input: "2017.06.27"] 7
    // => ["27", "27", index: 8, input: "2017.06.27"] 10
    

    使用match 全局获取页面中标签内容,但并不会返回匹配细节,

    使用matchAll方法可以返回迭代对象,显示更多的细节

    let str = "houdunren";
    let reg = /[a-z]/ig;
    for (const iterator of str.matchAll(reg)) {
        console.log(iterator);
    }
    

    (4) 替换

    var string = "2017-06-26";
    var today = new Date( string.replace(/-/g, "/") );
    console.log( today );
    // => Mon Jun 26 2017 00:00:00 GMT+0800 (中国标准时间)
    

    (5) 查找

    var regex = /\d/;
    var string = "abc123";
    console.log(regex.search(regex))//3
    
  10. 配合字符串方法

    (1) split(分隔符,分隔出来的字段的最大长度)方法

    var string = "html,css,javascript";
    console.log( string.split(/,/, 2) );
    // =>["html", "css"]
    

    正则使用分组时,结果数组中是包含分隔符的:

    var string = "html,css,javascript";
    console.log( string.split(/(,)/) );
    // =>["html", ",", "css", ",", "javascript"]
    

    (2) replace(正则表达式,字符串)

    当第二个参数是字符串时,如下的字符有特殊的含义

    属性描述
    1,1,2,…,$99匹配第 1-99 个 分组里捕获的文本
    $&匹配到的子串文本
    $ `匹配到的子串的左边文本
    $ '匹配到的子串的右边文本
    $$美元符号

    例如,把 "2,3,5",变成 "5=2+3":

    var result = "2,3,5".replace(/(\d+),(\d+),(\d+)/, "$3=$1+$2");
    console.log(result);
    // => "5=2+3"
    

    又例如,把 "2,3,5",变成 "222,333,555":

    var result = "2,3,5".replace(/(\d+)/g, "$&$&$&");
    console.log(result);
    // => "222,333,555"
    

    再例如,把 "2+3=5",变成 "2+3=2+3=5=5":

    var result = "2+3=5".replace(/=/, "$&$`$&$'$&");
    console.log(result);
    // => "2+3=2+3=5=5"
    

    说明: 要把 "2+3=5",变成 "2+3=2+3=5=5",其实就是想办法把 = 替换成 =2+3=5=,其中,$& 匹配的是 =, $` 匹配的是 2+3,' 匹配的是 5。因此使用 "\&`&'&" 便达成了目的。

    (3) replace(正则表达式,回调函数)

    "1234 2345 3456".replace(/(\d)\d{2}(\d)/g, function (match, $1, $2, index, input) {
    console.log([match, $1, $2, index, input]);
    });
    // => ["1234", "1", "4", 0, "1234 2345 3456"]
    // => ["2345", "2", "5", 5, "1234 2345 3456"]
    // => ["3456", "3", "6", 10, "1234 2345 3456"]
    
  11. 修饰符

    修饰符描述
    g全局匹配,即找到所有匹配的,单词是 global
    i匹配的时候不区分字母大小写,单词是 ingoreCase
    m多行匹配,除了字符串的开头和结尾。元字符””,将匹配到”\n”字符前;元字符,将匹配到”\n”字符后。单词是multiline。(本质改变”,将匹配到”\n”字符前;元字符”^”,将匹配到”\n”字符后。单词是 multiline。(本质改变了^ 和 的匹配范围)
    s视为单行忽略换行符,使用. 可以匹配所有字符(包括换行符),单词是Singleline (本质改变了. 的匹配配范围)
    lastIndexregexp.lastIndex 开始匹配
    u正确处理四个字符的 UTF-16 编码
    y

    (1)g 全局匹配

    不使用g,将匹配成功一次就停止

    let str = "AaaaaAAAcC";
    let result = str.replace(/a/, "s");
    console.log(result);//AsaaaAAAcC
    

    使用g

    let str = "AaaaaAAAcC";
    let result = str.replace(/a/g, "s");
    console.log(result);//AssssAAAcC
    

    (2) i 忽略大小写

    let str = "AaaaaAAAcC";
    let result = str.replace(/a/gi, "s");
    console.log(result);//sssssssscC
    

    (3) m多行匹配,在默认状态下,一个字符串无论是否换行只有一个开始^和结尾,如果采用多行匹配,那么每一个行都有一个开结尾,如果采用多行匹配,那么每一个行都有一个开头^和结尾

    ① 没有使用m,下面代码不能够匹配字符串"an",尽管"an"后面已经换行了,但是并没有采用多行匹配,所以不是字符串行的结尾

    var str = "This is an\n antzone good";
    var reg = /an$/;
    console.log(str.match(reg));//null
    

    ② 使用m(改变了)

    var reg = /^b/m;
    var str = 'test\nbbs';
    console.log(str.match(reg));
    //["b", index: 5, input: "test↵bbs", groups: undefined]
    

    (4) s单行匹配

    var reg = /.*/;
    var str = 'test\nbbs';
    console.log(str.match(reg));
    // ["test", "", "bbs", ""]
    
    var reg = /.*/gs;
    var str = 'test\nbbs';
    console.log(str.match(reg));
    //["test↵bbs", ""]
    

    (5) u模式

    let p = "Where there is a will, there is a way-有志者,事竟成!";
    //匹配所有的标点符号
    console.log(p.match(/\p{P}/gu));
    //匹配所有的字母
    console.log(p.match(/\p{L}/gu));
    //匹配中文
    console.log(p.match(/\p{sc=Han}/gu))//["有", "志", "者", "事", "竟", "成"]
    
    //使用 u 模式可以正确处理四个字符的 UTF-16 字节编码
    let str = "𝒳𝒴";
    console.table(str.match(/[𝒳𝒴]/)); //结果为乱字符"�"
    console.table(str.match(/[𝒳𝒴]/u)); //结果正确 "𝒳"
    

    (6) lastIndex

    ​ RegExp对象lastIndex 属性可以返回或者设置正则表达式开始匹配的位置

    ​ 必须结合 g 修饰符使用

    ​ 对 exec 方法有效

    ​ 匹配完成时,lastIndex 会被重置为0

    let str = "Where there is a will, there is a way."
    let reg = /\w/g;
    reg.lastIndex = 10;
    console.log(reg.exec(str));
    //=> ["e", index: 10, input: "Where there is a will, there is a way.", groups: undefined]
    console.log(reg.lastIndex);//11
    console.log(reg.exec(str));
    //=>["i", index: 12, input: "Where there is a will, there is a way.", groups: undefined]
    console.log(reg.lastIndex);//12
    

    (7) y模式

    //使用y 模式后如果从 lastIndex 开始匹配不成功就不继续匹配了
    let str = `紧急电话有:110,119,120`;
    let reg = /(\d+),?/y;
    reg.lastIndex = 7;//必须从第7、11、15个字符串开始才匹配成功
    while (res = reg.exec(str)) console.log(res);
    
  12. 正则的属性

    模式属性

    var regex = /\w/img;
    console.log( regex.global );
    console.log( regex.ignoreCase );
    console.log( regex.multiline );
    // => true
    // => true
    // => true
    

    source属性(打印字面量模式字符串)

    var className = "high";
    var regex = new RegExp("(^|\\s)" + className + "(\\s|$)");
    console.log( regex.source )
    // => (^|\s)high(\s|$)