JavaScript 与正则表达式 -- 括号

在正则表达式中,括号涉及的问题比较多,所以这里单独拿出来讲。

分组

如果量词所限定的元素不是一个字符或者字符组,而是一系列字符或者子表达式,就需要使用括号将他们括起来,表示为“一组”,构成单个元素

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

上面的例子中,量词 + 的前面的元素是 (ab) , 所以 + 所限定的是括号内 ab 这个整体。

划定多选结构的范围

多选结构, 也叫 分支结构。一般的用法: (p1|p2|p3),其中,| 表示 “或”,p1p2p3 是三个子表达式,这些子表达式也叫多选分支, 括号用来划定分支结构的范围。
注意:多选结构中括号不是必须的。如果没有括号,管道符 | 会把整个表达式当做一个多选结构。比如,要匹配 grey或gray:

var regexRight = /gr(e|a)y/;  // 匹配 grey 或 gray
var regexWrong = /gre|ay/;  // 匹配 gre 或 ay

// 正确的
console.log(regexRight.test('grey'));  // true
console.log(regexRight.test('gray'));  // true
console.log(regexRight.test('gre'));  // false

// 错误的
console.log(regexWrong.test('grey'));  // true
console.log(regexWrong.test('gre'));   // true

所以,虽然多选结构中括号不是必须的,但是,通常会搭配括号来使用。

多选结构与字符组

上面多选结构中 gr(e|a)y的例子并太好,因为可以使用更好的方式代替,那便是 gr[ae]y,那么二者什么区别呢?
二者差别还是很大的:

  • 多选结构中每个分支都必须明确列出。而字符组可以使用 - 表示范围
  • 大多数情况下, [abc] 要比 (a|b|c) 更高效
  • 字符组的每个 “分支” 都必须是单个的字符,而多选结构的“分支”可以是子表达式
  • 多选结构的分支顺序会影响到最后的配置结果
  • 没有 排除型多选结构

引用分组

使用括号之后,正则表示会保存每个分组真正匹配的文本,等匹配成功后,可以引用这些文本。
因为这种情况下“捕获”了文本,所以这种分组叫 捕获分组,这种括号叫 捕获型括号

通过编号引用

编号规则:
如,使用(\d{4})-(\d{2})-(\d{2})匹配日期 2018-12-30:

字符串 2018 12 30
表达式 (\d{4}) (\d{2}) (\d{2})
分组编号 1 2 3

注意:
如果把表达式写成:(\d){4}-(\d){2}-(\d){2},则含义完全不同,(\d){4} 表示 \d 作为单独的元素出现4次,且编号都为1。

嵌套规则:根据开括号的出现顺序来计数。(图参考《正则指引》P45,我画的有点丑)

括号嵌套编号规则:开括号的出现顺序

在 JavaScript 中使用

提取数据

String.prototype.match() 方法返回一个数组,数组的第一项是进行匹配的完整字符串,之后的项是捕获分组的匹配结果。

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var text = '2018-12-30';
console.log(text.match(regex));
// ["2018-12-30", "2018", "12", "30", index: 0, input: "2018-12-30"]

关于 match 方法,有一个地方需要注意,返回结果与正则表达式是否包含 g 标志有关。在没有 g 标志的时候,返回值和 regex.exec() 方法相同:

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var text = '2018-12-30';
console.log(regex.exec(text));
// ["2018-12-30", "2018", "12", "30", index: 0, input: "2018-12-30"]

同时,也可以使用构造函数的全局属性 $1$9 来获取引用:

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var text = '2018-12-30';
regex.exec(text);

console.log(RegExp.$1);  // 2018
console.log(RegExp.$2);  // 12
console.log(RegExp.$3);  // 30

替换

比如,想把 yyyy-mm-dd 格式,替换成 mm/dd/yyyy 怎么做?
可以使用下面的三种方法:

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var text = '2018-12-30';

// 1
var result1 = text.replace(regex, '$2/$3/$1');

// 2
var result2 = text.replace(regex, () => `${RegExp.$2}/${RegExp.$3}/${RegExp.$1}`);

// 3
var result3 = text.replace(regex, (str, y, m, d) => `${m}/${d}/${y}`);

console.log(result1);    // 12/30/2018
console.log(result2);    // 12/30/2018
console.log(result3);    // 12/30/2018

String.prototype.replace() 规则相对复杂,有很多玩法,了解更多

反向引用

在正则表达式内部引用之前(左侧)捕获分组匹配的文本,形式如:\num ,其中 num 表示编号,编号规则与之前介绍的相同。
举个例子:
比如要匹配: 2018-12-302018.12.302018/12/30 三种形式。
可能首先想到的是:\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2},但是:

var regex = /\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/;
var text = '2018-12.30';
console.log(regex.test(text));  // true

显然,我们不希望匹配 2018-12.30 ,我们需要前后的分隔符相同:

var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
var text1 = '2018-12.30';
var text2 = '2018-12-30';
var text3 = '2018/12/30';

console.log(regex.test(text1));  // false
console.log(regex.test(text2));  // true
console.log(regex.test(text3));  // true

这里的 \1 就是对前面 (-|\/|\.) 的引用,表达式可视化如下:

反向引用可视化

反向引用的二义性:

在反向引用中,如果编号大于9就会出现二义性,如:\10 是表示第十个捕获分组呢还是表示第一个捕获分组和一个字符 0 呢?
在一些编程语言中有专门的规定来避免二义性,但是在JavaScript中并没有,JavaScript对于 \10 的处理是:

  1. 如果存在第 10 个捕获分组,则引用对应的分组
  2. 如果不存在,则引用 \1

如果,在有第 10 个捕获分组的情况下,要匹配 \1 和 字符0 的话,可以使用下面两种方法:

  • 命名分组
  • 再使用括号将 \10 括起来,比如 (\1)0\1(?:0)

命名分组

由于按编号引用分组存在一些问题,如:可读性差,不易维护,二义性等。于是出现了命名分组,使用易记忆,易辨别的名字来代替编号。
注意:命名分组是 ES2017 新特性。

语法规则如下:

  • 分组:(?<name>)
  • 提取:$<name>
  • 反向引用:\k<name>

比如,上文的一个例子可以改为:

var regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
var text = '2018-12-30';
var result = text.replace(regex, '$<month>/$<day>/$<year>');

console.log(result);   // 12/30/2018

对于方法 String.prototype.match()RegExp.prototype.exec() 也有了新玩法:

var regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
var text = '2018-12-30';
var matchObj = text.match(regex);

console.log(matchObj.groups);
// {year: "2018", month: "12", day: "30"}

在匹配结果中,多了 groups 属性,保存了所有命名捕获分组的匹配结果。

再来看一个反向引用的例子:

var regex = /\d{4}(?<split>-|\/|\.)\d{2}\k<split>\d{2}/;
var text = '2018-12-30';

console.log(regex.test(text));  // true

非捕获分组

括号的功能有“叠加”性。括号可以表示分组,用来构成单个元素;也可以表示多选结构;但同时,也构成了引用分组。
在仅仅需要标记范围(分组或多选结构)时,正则表达式保存已经匹配的文本会造成不必要的性能浪费。
这时候我们可以使用 非捕获型括号 (?:...)来限定分组或多选结构的范围:(?:p)(?:p1|p2)。这种只用来限定范围不捕获匹配文本的分组就是 非捕获分组

非捕获型分组的优点是性能好,缺点是不美观,可读性差。
在实际应用中,建议尽量使用非捕获分组。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • 个人根据《正则指引》内容总结记录,侵删!! 转载至我的博客 最近看了编译原理方面的书,觉得正则表达式非常重要,在各...
    J退後阅读 597评论 0 0
  • 初衷:看了很多视频、文章,最后却通通忘记了,别人的知识依旧是别人的,自己却什么都没获得。此系列文章旨在加深自己的印...
    DCbryant阅读 3,982评论 0 20
  • 正则表达式有很多流派,也有很多的特性,不同的语言支持度也是不一样的。本篇文章是写Python中的正则表达式的用法的...
    Moscow1147阅读 1,083评论 0 0
  • 从匹配中返回值 Match 对象 成功的匹配总是返回一个 Match 对象, 这个对象通常也被放进 $/ 中, (...
    焉知非鱼阅读 1,784评论 0 1
  • 专业考题类型管理运行工作负责人一般作业考题内容选项A选项B选项C选项D选项E选项F正确答案 变电单选GYSZ本规程...
    小白兔去钓鱼阅读 8,977评论 0 13