ECMAScript特性:正则表达式模式修饰符

158 阅读3分钟

本文为翻译
本文译者为 360 奇舞团前端开发工程师
原文标题:ECMAScript feature: regular expression pattern modifiers
原文作者:Dr. Axel Rauschmayer
原文地址:2ality.com/2025/01/reg…

传统上,我们只能将诸如 i(用于忽略大小写)之类的正则表达式标志应用于整个正则表达式。ECMAScript特性“正则表达式模式修饰符”(由 Ron Buckton 提出)使我们能够仅将这些标志应用于正则表达式的部分内容。在这篇文章中,我们将探讨它们的工作原理以及使用场景。

正则表达式模式修饰符属性在2024年10月进入第4阶段,很可能会成为ECMAScript 2025的一部分。

什么是模式修饰符?

正则表达式的工作方式受所谓的标志影响,例如,标志 i 在匹配时忽略大小写:

> /yes/i.test('yes')
true
> /yes/i.test('YES')
true

模式修饰符使我们能够将标志应用于正则表达式的部分内容(而非整个正则表达式)。例如,在以下正则表达式中,标志 i 仅应用于“HELLO”:

> /^x(?i:HELLO)x$/.test('xHELLOx')
true
> /^x(?i:HELLO)x$/.test('xhellox')
true
> /^x(?i:HELLO)x$/.test('XhelloX')
false

从某种程度上说,模式修饰符就是内联标志。

语法

语法形式如下:

(?ims-ims:pattern)
(?ims:pattern)
(?-ims:pattern)

注意事项

  • 问号(?)后面的标志为启用状态。
  • 连字符(-)后面的标志为禁用状态。
  • 一个标志不能同时出现在“启用部分”和“禁用部分”。
  • 不带任何标志时,此语法只是一个非捕获组:(?:pattern)

让我们修改前面的示例:现在除了“HELLO”之外,整个正则表达式都不区分大小写:

> /^x(?-i:HELLO)x$/i.test('xHELLOx')
true
> /^x(?-i:HELLO)x$/i.test('XHELLOX')
true
> /^x(?-i:HELLO)x$/i.test('XhelloX')
false

支持哪些标志?

模式修饰符中可使用以下标志:

字面标志属性名ES版本描述
iignoreCaseES3执行不区分大小写的匹配
mmultilineES3^ 和 $ 匹配每行的开头和结尾
sdotAllES2018点号(.)匹配行终止符

欲了解更多信息,请参阅《探索JavaScript》中关于标志的部分。

其余标志不被支持,原因要么是它们会使正则表达式语义过于复杂(例如标志 v),要么是它们只有应用于整个正则表达式才有意义(例如标志 g)。

模式修饰符的使用场景有哪些?

使用场景:更改正则表达式部分内容的标志

有时,能够更改正则表达式部分内容的标志是很有用的。例如,Ron Buckton 解释说,更改标志 m 有助于匹配文件开头的Markdown前置元数据块(我对他的版本稍作修改):

const re = /(?-m:^)---\r?\n((?:^(?!---$).*\r?\n)*)^---$/m;
assert.equal(re.test('---a'), false);
assert.equal(re.test('---\n---'), true);
assert.equal(
  re.exec('---\n---')[1],
  ''
);
assert.equal(
  re.exec('---\na: b\n---')[1],
  'a: b\n'
);

这个正则表达式是如何工作的呢?

  • 默认情况下,标志 m 处于启用状态,锚点 ^ 匹配行首,锚点 $ 匹配行尾。
  • 第一个 ^ 有所不同:它必须匹配字符串的开头。这就是为什么我们在那里使用模式修饰符并关闭标志 m

下面是添加了无关空白和解释性注释的正则表达式:

(?-m:^)---\r?\n  # 字符串的第一行
(  # 用于前置元数据的捕获组
  (?:  # 一行的模式(非捕获组)
    ^(?!---$)  # 行不能以 "---" + 行结束符开头(正向先行断言)
.*\r?\n
  )*
)
^---$  # 前置元数据的结束分隔符

使用场景:内联标志

在某些情况下,标志位于实际正则表达式之外会带来不便。这时模式修饰符就派上用场了。例如:

  • 将正则表达式存储在配置文件中,例如以JSON格式存储。
  • Regex+库提供了一种模板字面量,使创建正则表达式更加方便。指定标志的语法会增加一些杂乱度,通过模式修饰符(如果它们支持所需的标志)可以避免这种情况:
regex('i')`world`
regex`(?i:world)`

使用场景:正则表达式片段可以更改标志

在复杂应用中,如果能够由较小的正则表达式组合成大型正则表达式会很有帮助。前面提到的Regex+库支持这种做法。如果一个较小的正则表达式需要不同的标志(例如因为它想要忽略大小写),那么借助模式修饰符就可以实现。

进一步阅读