翻译:疯狂的技术宅
原文:https://www.smashingmagazine....


本文首发微信公众号:jingchengyideng
欢迎关注,每天都给你推送新鲜的前端技术文章


摘要:如果你曾用 JavaScript 做过复杂的文本处理和操作,那么你将会对 ES2018 中引入的新功能爱不释手。 在本文中,我们将详细介绍第 9 版标准如何提高 JavaScript 的文本处理能力。


有一个很好的理由能够解释为什么大多数编程语言都支持正则表达式:它们是用于处理文本的极其强大的工具。 通常一行正则表达式代码就能完成需要几十行代码才能搞定的文本处理任务。 虽然大多数语言中的内置函数足以对字符串进行一般的搜索和替换操作,但更加复杂的操作(例如验证文本输入)通常需要使用正则表达式。

自从 1999 年推出 ECMAScript 标准第 3 版以来,正则表达式已成为 JavaScript 语言的一部分。ECMAScript 2018(简称ES2018)是该标准的第 9 版,通过引入四个新功能进一步提高了JavaScript的文本处理能力:

  • 后行断言
  • 命名捕获组
  • s (dotAll) flag
  • Unicode属性转义

下面详细介绍这些新功能。

后行断言

能够根据之后或之前的内容匹配一系列字符,使你可以丢弃可能不需要的匹配。 当你需要处理大字符串并且意外匹配的可能性很高时,这个功能非常有用。 幸运的是,大多数正则表达式都为此提供了 lookbehind 和 lookahead 断言。

在 ES2018 之前,JavaScript 中只提供了先行断言。 lookahead 允许你在一个断言模式后紧跟另一个模式。

先行断言有两种版本:正向和负向。 正向先行断言的语法是 (?=...)。 例如,正则表达式 /Item(?= 10)/ 仅在后面跟随有一个空格和数字 10 的时候才与 Item 匹配:

const re = /Item(?= 10)/;console.log(re.exec('Item'));
// → nullconsole.log(re.exec('Item5'));
// → nullconsole.log(re.exec('Item 5'));
// → nullconsole.log(re.exec('Item 10'));
// → ["Item", index: 0, input: "Item 10", groups: undefined]

此代码使用 exec() 方法在字符串中搜索匹配项。 如果找到匹配项, exec() 将返回一个数组,其中第一个元素是匹配的字符串。 数组的 index 属性保存匹配字符串的索引, input 属性保存搜索执行的整个字符串。 最后,如果在正则表达式中使用了命名捕获组,则将它们放在 groups 属性中。 在代码中, groups 的值为 undefined ,因为没有被命名的捕获组。

负向先行的构造是 (?!...) 。 负向先行断言的模式后面没有特定的模式。 例如, /Red(?!head)/ 仅在其后不跟随 head 时匹配 Red

const re = /Red(?!head)/;console.log(re.exec('Redhead'));
// → nullconsole.log(re.exec('Redberry'));
// → ["Red", index: 0, input: "Redberry", groups: undefined]console.log(re.exec('Redjay'));
// → ["Red", index: 0, input: "Redjay", groups: undefined]console.log(re.exec('Red'));
// → ["Red", index: 0, input: "Red", groups: undefined]

ES2018 为 JavaScript 补充了后行断言。 用 (?<=...) 表示,后行断言允许你在一个模式前面存在另一个模式时进行匹配。

假设你需要以欧元检索产品的价格但是不捕获欧元符号。 通过后行断言,会使这项任务变得更加简单:

const re = /(?<=€)\d+(\.\d*)?/;console.log(re.exec('199'));
// → nullconsole.log(re.exec('$199'));
// → nullconsole.log(re.exec('€199'));
// → ["199", undefined, index: 1, input: "€199", groups: undefined]

注意先行(Lookahead)和后行(lookbehind)断言通常被称为“环视”(lookarounds)

后行断言的反向版本由 (?<!...) 表示,使你能够匹配不在lookbehind中指定的模式之前的模式。 例如,正则表达式 /(?<!\d{3}) meters/ 会在 三个数字不在它之前 匹配单词“meters”如果:

const re = /(?<!\d{3}) meters/;console.log(re.exec('10 meters'));
// → [" meters", index: 2, input: "10 meters", groups: undefined]console.log(re.exec('100 meters'));
// → null

与前行断言一样,你可以连续使用多个后行断言(负向或正向)来创建更复杂的模式。下面是一个例子:

const re = /(?<=\d{2})(?<!35) meters/;console.log(re.exec('35 meters'));
// → nullconsole.log(re.exec('meters'));
// → nullconsole.log(re.exec('4 meters'));
// → nullconsole.log(re.exec('14 meters'));
// → ["meters", index: 2, input: "14 meters", groups: undefined]

此正则表达式仅匹配包含“meters”的字符串,如果它前面紧跟 35 之外的任何两个数字。正向后行确保模式前面有两个数字,同时负向后行能够确保该数字不是 35。

命名捕获组

你可以通过将字符封装在括号中的方式对正则表达式的一部分进行分组。 这可以允许你将规则限制为模式的一部分或在整个组中应用量词。 此外你可以通过括号来提取匹配值并进行进一步处理。

下列代码给出了如何在字符串中查找带有 .jpg 并提取文件名的示例:

const re = /(\w+)\.jpg/;
const str = 'File name: cat.jpg';
const match = re.exec(str);
const fileName = match[1];// The second element in the resulting array holds the portion of the string that parentheses matched
console.log(match);
// → ["cat.jpg", "cat", index: 11, input: "File name: cat.jpg", groups: undefined]console.log(fileName);
// → cat

在更复杂的模式中,使用数字引用组只会使本身就已经很神秘的正则表达式的语法更加混乱。 例如,假设你要匹配日期。 由于在某些国家和地区会交换日期和月份的位置,因此会弄不清楚究竟哪个组指的是月份,哪个组指的是日期:

const re = /(\d{4})-(\d{2})-(\d{2})/;
const match = re.exec('2020-03-04');console.log(match[0]);    // → 2020-03-04
console.log(match[1]);    // → 2020
console.log(match[2]);    // → 03
console.log(match[3]);    // → 04

ES2018针对此问题的解决方案名为捕获组,它使用更具表现力的 (?<name>...) 形式的语法:

const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = re.exec('2020-03-04');console.log(match.groups);          // → {year: "2020", month: "03", day: "04"}
console.log(match.groups.year);     // → 2020
console.log(match.groups.month);    // → 03
console.log(match.groups.day);      // → 04

因为生成的对象可能会包含与命名组同名的属性,所以所有命名组都在名为 groups 的单独对象下定义。

许多新的和传统的编程语言中都存在类似的结构。 例如Python对命名组使用 (?P<name>) 语法。 Perl支持与 JavaScript 相同语法的命名组( JavaScript 已经模仿了 Perl 的正则表达式语法)。 Java也使用与Perl相同的语法。

除了能够通过 groups 对象访问命名组之外,你还可以用编号引用访问组—— 类似于常规捕获组:

const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = re.exec('2020-03-04');console.log(match[0]);    // → 2020-03-04
console.log(match[1]);    // → 2020
console.log(match[2]);    // → 03
console.log(match[3]);    // → 04

新语法也适用于解构赋值:

const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const [match, year, month, day] = re.exec('2020-03-04');console.log(match);    // → 2020-03-04
console.log(year);     // → 2020
console.log(month);    // → 03
console.log(day);      // → 04

即使正则表达式中不存在命名组,也始终创建 groups 对象:

const re = /\d+/;
const match = re.exec('123');console.log('groups' in match);    // → true

如果可选的命名组不参与匹配,则 groups 对象仍将具有命名组的属性,但该属性的值为 undefined

const re = /\d+(?<ordinal>st|nd|rd|th)?/;let match = re.exec('2nd');console.log('ordinal' in match.groups);    // → true
console.log(match.groups.ordinal);         // → ndmatch = re.exec('2');console.log('ordinal' in match.groups);    // → true
console.log(match.groups.ordinal);         // → undefined

你可以稍后在模式中引用常规捕获的组,并使用 \1 的形式进行反向引用。 例如以下代码使用在行中匹配两个字母的捕获组,然后在模式中调用它:

console.log(/(\w\w)\1/.test('abab'));    // → true// if the last two letters are not the same
// as the first two, the match will fail
console.log(/(\w\w)\1/.test('abcd'));    // → false

要在模式中稍后调用命名捕获组,可以使用 /\k<name>/ 语法。 下面是一个例子:

const re = /\b(?<dup>\w+)\s+\k<dup>\b/;const match = re.exec("I'm not lazy, I'm on on energy saving mode");        console.log(match.index);    // → 18
console.log(match[0]);       // → on on

此正则表达式在句子中查找连续的重复单词。 如果你愿意,还可以用带编号的后引用来调用命名的捕获组:

const re = /\b(?<dup>\w+)\s+\1\b/;const match = re.exec("I'm not lazy, I'm on on energy saving mode");        console.log(match.index);    // → 18
console.log(match[0]);       // → on on 

也可以同时使用带编号的后引用和命名后向引用:

const re = /(?<digit>\d):\1:\k<digit>/;const match = re.exec('5:5:5');        console.log(match[0]);    // → 5:5:5

与编号的捕获组类似,可以将命名的捕获组插入到 replace() 方法的替换值中。 为此,你需要用到 $<name> 构造。 例如:

const str = 'War & Peace';console.log(str.replace(/(War) & (Peace)/, '$2 & $1'));
// → Peace & Warconsole.log(str.replace(/(?<War>War) & (?<Peace>Peace)/, '$<Peace> & $<War>'));
// → Peace & War

如果要使用函数执行替换,则可以引用命名组,方法与引用编号组的方式相同。 第一个捕获组的值将作为函数的第二个参数提供,第二个捕获组的值将作为第三个参数提供:

const str = 'War & Peace';const result = str.replace(/(?<War>War) & (?<Peace>Peace)/, function(match, group1, group2, offset, string) {return group2 + ' & ' + group1;
});console.log(result);    // → Peace & War

s (dotAll) Flag

默认情况下,正则表达式模式中的点 (.) 元字符匹配除换行符 (\n) 和回车符 (\r)之外的所有字符:

console.log(/./.test('\n'));    // → false
console.log(/./.test('\r'));    // → false

尽管有这个缺点,JavaScript 开发者仍然可以通过使用两个相反的速记字符类来匹配所有字符,例如[ w W],它告诉正则表达式引擎匹配一个字符(\w)或非单词字符(\W):

console.log(/[\w\W]/.test('\n'));    // → true
console.log(/[\w\W]/.test('\r'));    // → true

ES2018旨在通过引入 s (dotAll) 标志来解决这个问题。 设置此标志后,它会更改点 (.)元字符的行为以匹配换行符:

console.log(/./s.test('\n'));    // → true
console.log(/./s.test('\r'));    // → true

s 标志可以在每个正则表达式的基础上使用,因此不会破坏依赖于点元字符的旧行为的现有模式。 除了 JavaScript 之外, s 标志还可用于许多其他语言,如 Perl 和 PHP。

Unicode 属性转义

ES2015中引入的新功能包括Unicode感知。 但是即使设置了 u 标志,速记字符类仍然无法匹配Unicode字符。

请考虑以下案例:

const str = '?';console.log(/\d/.test(str));     // → false
console.log(/\d/u.test(str));    // → false

?被认为是一个数字,但 \d 只能匹配ASCII [0-9],因此 test() 方法返回 false。 因为改变速记字符类的行为会破坏现有的正则表达式模式,所以决定引入一种新类型的转义序列。

在ES2018中,当设置 u 标志时,Unicode属性转义(由 \p{...} 表示)在正则表达式中可用。 现在要匹配任何Unicode 数字,你只需使用 \p{Number},如下所示:

const str = '?';
console.log(/\p{Number}/u.test(str));     // → true

要匹配 Unicode 字符,你可以使用\p{Alphabetic}

const str = '漢';console.log(/\p{Alphabetic}/u.test(str));     // → true// the \w shorthand cannot match 漢
console.log(/\w/u.test(str));    // → false

\P{...}\p{...} 的否定版本,并匹配 \p{...} 没有的所有字符:

console.log(/\P{Number}/u.test('?'));    // → false
console.log(/\P{Number}/u.test('漢'));    // → trueconsole.log(/\P{Alphabetic}/u.test('?'));    // → true
console.log(/\P{Alphabetic}/u.test('漢'));    // → false

当前规范提案中提供了受支持属性的完整列表。

请注意,使用不受支持的属性会导致 SyntaxError

console.log(/\p{undefined}/u.test('漢'));    // → SyntaxError

兼容性列表

桌面浏览器

Chrome Firefox Safari Edge
后行断言 62 X X X
命名捕获组 64 X 11.1 X
s (dotAll) Flag 62 X 11.1 X
Unicode 属性转义 64 X 11.1 X

移动浏览器

Chrome For Android Firefox For Android iOS Safari Edge Mobile Samsung Internet Android Webview
后行断言 62 X X X 8.2 62
命名捕获组 64 X 11.3 X X 64
s (dotAll) Flag 62 X 11.3 X 8.2 62
Unicode 属性转义 64 X 11.3 X X 64

NODE.JS

  • 8.3.0 (需要 --harmony 运行时标志)
  • 8.10.0 (支持 s (dotAll) flag 和后行断言)
  • 10.0.0 (完全支持)

总结

通过使正则表达式得到增强,ES2018 继续了以前版本ECMAScript的工作。新功能包括后行断言,命名捕获组, s (dotAll) flag 和 Unicode属性转义。 后行断言允许你在一个模式前面存在另一个模式进行匹配。与常规捕获组相比,命名捕获组使用了更具表现力的语法。 s (dotAll) flag 通过更改点(.)元字符的行为来匹配换行符。最后,Unicode 属性转义在正则表达式中提供了一种新类型的转义序列。

在构建复杂的模式时,使用正则表达式测试程序通常很有帮助。一个好的测试器会提供一个接口来对字符串的正则表达式进行测试,并显示引擎所做的每一步,这在你理解其他人编写的表达式时非常有帮助。它还可以检测正则表达式中可能出现的语法错误。 Regex101 和 RegexBuddy 是两个值得一试的正则表达式测试程序。

除此之外你能推荐其他的工具吗?欢迎在评论中分享!


本文首发微信公众号:jingchengyideng

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章



正则表达式在 ES2018 中的新写法相关推荐

  1. vue2.x中slot-scope插槽在vue3.x中的新写法

    vue2.x中slot-scope插槽在vue3.x中的新写法 1.vue2.x的写法 <el-table-column label="测试" align="cen ...

  2. c#使用正则表达式获取TR中的多个TD_[Python从零到壹] 四.网络爬虫之入门基础及正则表达式抓取博客案例...

    首先祝大家中秋节和国庆节快乐,欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都 ...

  3. Spring 2.5:Spring MVC中的新特性

    转载说明:infoQ就是牛人多,看人家去年就把Spring2.5注视驱动的MVC写出来了,还是这么详细,我真是自叹不如,今天偶尔看到这篇文章非常认真的拜读了2遍,简直是茅厕顿开啊....\(^o^)/ ...

  4. .net ajax 怎么写,.NET学习篇--Ajax中Url的写法

    .NET学习篇--Ajax中Url的写法 什么是Ajax AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML). AJAX 不是新的 ...

  5. python中怎么写注释_Python中注释的写法

    Python中注释的写法 #:使用井号进行单行注释 Python中貌似没有提供多行注释,不过我们可以利用三引号的多行字符串来进行多行注释 """ 多行注释内容 多行注释内 ...

  6. IE6.0、IE7.0 、FireFox 在样式中的不同写法.doc

    IE6.0.IE7.0 .FireFox 在样式中的不同写法 2009年3月26日 15:44:27 发布:hugo 或许你一直在抱怨为什么要专门为IE和FF写不同的CSS,为什么IE这样让人头疼,然 ...

  7. mysql添加新的实例_MySQL中添加新用户权限的实例详解

    有2个不同的方法增加用户:通过使用GRANT语句或通过直接操作MySQL授权表.比较好的方法是使用GRANT语句,因为他们是更简明并且好像错误少些. 下面的例子显示出如何使用MySQL客户安装新用户. ...

  8. mysql触发器可以使用正则表达式_SQL 正则表达式及mybatis中使用正则表达式

    这篇文章主要介绍了SQL 正则表达式及mybatis中使用正则表达式的方法,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下 mysql 提供的模式匹配的其他类型是使用扩展正则表达式. 当你对这 ...

  9. SQL Server 2016中的新PowerShell Cmdlet

    介绍 (Introduction) PowerShell is Windows Shell that can be used to automate tasks in Windows, Exchang ...

  10. 当ViewModelProviders方法被弃用,使用ViewModelProvider获取ViewModel的新写法_莫韵乐与bug的奇妙冒险

    当ViewModelProviders方法被弃用,使用ViewModelProvider获取ViewModel的新写法 在androidx.lifecycle:lifecycle-extensions ...

最新文章

  1. 最大流 ---- 最大不相交路径数 ---- P2766 最长不下降子序列问题(网络流24题)
  2. ZIP,一个没落天才的故事
  3. Cassandra-Java(增删查改)
  4. python如何编写excel_如何用Python编写Excel
  5. Binder源码分析之Java层(原)
  6. keras从入门到放弃(十二)卷积神经网络
  7. Win32ASM学习[21]:宏汇编(1)
  8. 28 FI配置-财务会计-外币评估-检查分配到分类账组的会计核算原则
  9. 社区发现 实践_实践社区可以为您的组织做什么
  10. 这位 GitHub 冠军项目背后的“老男人”,堪称 10 倍程序员本尊!
  11. LR回归原理和损失函数的推导
  12. Java实现自动映射原生JDBC查询出的数据库字段
  13. 微信公众号菜单html5,微信公众号自定义菜单全攻略
  14. tensorflow目标检测API实现血细胞图像识别和计数
  15. 记录一个看着比较顺眼的blockquote的css装饰
  16. 前端html与css学习笔记总结篇(超详细)
  17. C#加载本地相对路径HTML页面
  18. 2018医学考博英语阅读理解解题技巧
  19. 平安云Redis实践降本增效的背后,竟然是它在支招?
  20. 赛尔102S助力云南开展2020年白马雪山国家级自然保护区低空无人机生态监测

热门文章

  1. 什么是Windows内核编程
  2. elementui表格列宽自适应_Java 操作Word表格——创建嵌套表格、添加复制表格行或列...
  3. macos无法验证此app不包含恶意软件_macOS 下 Electron 程序的签名及公证
  4. 计算机msoffice怎么复习,全国计算机一级MSoffice怎样复习我在 – 手机爱问
  5. Java学习之路 之 容易混淆篇
  6. 《Android开发艺术探索》— Android 书籍
  7. Flutter报错 Navigator operation requested with a context that does not include a Navigator.
  8. 7-8 评委打分 (5 分)
  9. RK3288_Android7.1调试RTC总结(二)
  10. 移动端网站如何开发(电脑端网站到手机端网站我们需要在html代码中添加哪个meta标签)...