正则表达式笔记(三)-括号

94 阅读3分钟

[JS正则迷你书作者](老姚 的个人主页 - 动态 - 掘金 (juejin.cn))

[JS正则迷你书](《JavaScript 正则表达式迷你书》问世了! - 知乎 (zhihu.com))

括号

分组和分支结构

    // 分组,匹配ab
    var regex = /(ab)+/// 分支结构
    var regex = /I Love (Javascript|Regular Expression )/

分组引用

这是括号一个重要的作用,有了它,我们就可以进行数据提取,以及更强大的替换操作。而要使用它带来的好处,必须配合使用实现环境的 API。

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

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

图片截自正则迷你书

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

图片截自正则迷你书

对比这两个可视化图片,我们发现,与前者相比,后者多了分组编号,如Group #1。

其实正则引擎也是这么做的,在匹配过程中,给每一个分组都开辟一个空间,用来存储每一个分组匹配到的数据。

既然分组可以捕获数据,那么我们就可以使用它们。

提取数据

    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"]
    ​
    regex3 = /(\d{4})-(\d{2})-(\d{2})/g
    console.log(string2.match(regex3));
    // => ["2017-06-12"]

match返回的一个数组,第一个元素是整体匹配结果,然后是各个分组(括号里)匹配的内容,然后是匹配下标,最后是输入的文本。另外,正则表达式是否有修饰符g,match返回的数组格式是不一样的

另外也可以使用正则实例对象的exec方法:

    var regex = /(\d{4})-(\d{2})-(\d{2})/g;
    var string = "2017-06-12";
    console.log(regex.exec(string));
    // => ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"]
    // 加不加g都一样

同时,也可以使用构造函数的全局属性11至9来获取(必须要先有正则操作,最后输出显示的会是最后一句返回true的或者匹配的上的正则操作):

    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"
    // 有$1-...-$_
    // $_表示输入的文本

替换数据

比如,想把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");
    // 将string中的regex匹配到的变成$2/$3/$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(){
        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){
        // match表示匹配到的总字符串
        // 具体的参数数量看有几个编组
        return month + "/" + day + "/" + year;
    });
    console.log(result);
    // => "06/12/2017"

反向引用-\1\2

除了使用相应 API 来引用分组,也可以在正则本身里引用分组。但只能引用之前出现的分组,即反向引用。

还是以日期为例。比如要写一个正则支持匹配如下三种格式:

2016-06-12

2016/06/12

2016.06.12

最先可能想到的正则是:

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

其中/和.需要转义。虽然匹配了要求的情况,但也匹配"2016-06/12"这样的数据。

假设我们想要求分割符前后一致怎么办?

此时需要使用反向引用:

    var regex = /\d{4}(-|/|.)\d{2}\1\d{2}/;
    // 其中的\1表示引用编组1,且匹配到的必须与编组1匹配到的一致!

图片截自正则迷你书

括号嵌套?

以左括号(开括号)为准。

    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

\10表示?

\10表示引用第十个编组,所以要是想分别匹配 \1 和 0,需要这么写: (?:\1)0\1(?:0) (非捕获括号)

引用不存在的分组?

匹配反向引用的字符本身。例如\2,就匹配"\2"。

注意"\2"表示对"2"进行了转义。

不同的浏览器和版本,打印的结果不一样。

image.png

image.png

分组后边有量词?

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

    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

非捕获括号

之前出现的括号,都会捕获它们匹配到的数据,以便后续引用,因此也称它们是捕获型分组和捕获型分支。

如果只想要括号最原始的功能,但不会引用它,即,既不在 API 里引用(RegExp.$1、exec方法、match方法),也不在正则里反向引用(\1\2\3... )。

此时可以使用非捕获括号 (?:p)(?:p1|p2|p3)

    var regex = /(?:ab)+/g; // 原:/(ab)+/g
    var string = "ababa abbb ababab";
    console.log(string.match(regex));
    // => ["abab", "ab", "ababab"]