ES6 - 正则的扩展

161 阅读4分钟

阮一峰 ECMAScript 6 入门

RegExp 构造函数

ES5 中,RegExp 构造函数有两种使用方法:

// 1. 参数是字符串,第二个参数是修饰符
var regex = new RegExp("xyz", "i");

// 2. 参数是正则表达式,返回 正则表达式的拷贝。此时不允许使用第二个参数,否则报错
var regex = new RegExp(/xyz/i);

ES6 中,如果 RegExp 构造函数的第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。而且,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。

字符串的正则方法

ES6 之前,字符串对象有 4 个方法可以使用正则表达式:match()replace()search()split()

ES6 将这 4 个方法,在语言内部全部调用 RegExp 的实例方法,从而做到所有与正则相关的方法,全都定义在 RegExp 对象上。

如: String.prototype.match 调用 RegExp.prototype[Symbol.match]

u 修饰符

ES6 对正则表达式添加了 u 修饰符,含义为“Unicode 模式”,用来正确处理大于 \uFFFF 的 Unicode 字符。

RegExp.prototype.unicode 属性

正则实例对象新增 unicode 属性,表示是否设置了 u 修饰符。

const r1 = /hello/;
const r2 = /hello/u;

r1.unicode; // false
r2.unicode; // true

y 修饰符

除了 u 修饰符,ES6 还为正则表达式添加了 y 修饰符,叫做“粘连”(sticky)修饰符。

RegExp.prototype.sticky 属性

y 修饰符相匹配,ES6 的正则实例对象多了 sticky 属性,表示是否设置了 y 修饰符。

var r = /hello\d/y;
r.sticky; // true

RegExp.prototype.flags 属性

ES6 为正则表达式新增了 flags 属性,会返回正则表达式的修饰符。

// ES5 中 source 属性,返回正则表达式的正文
/abc/gi.source;
// "abc"

/abc/gi.flags;
// 'gi'

s 修饰符:dotAll 模式

正则表达式中,点(.)代表任的单个字符,但是有两个例外。

  • 一个是四个字节的 UTF-16 字符,这个可以用 u 修饰符解决;
  • 另一个是行终止符(line terminator character)

有以下四个“行终止符”:

  • U+000A 换行符(\n)
  • U+000D 回车符(\r)
  • U+2028 行分隔符(line separator)
  • U+2029 段分隔符(paragraph separator)
/foo.bar/.test("foo\nbar"); // false

ES2018 引入 s 修饰符,使得.可以匹配任意单个字符。

/foo.bar/s.test("foo\nbar"); // true

这被称为 dotAll 模式,即点(dot)代表一切字符。所以,正则表达式还引入了一个 dotAll 属性,返回一个布尔值,表示该正则表达式是否处在 dotAll 模式。

后行断言

JavaScript 语言的正则表达式,只支持先行断言(lookahead)先行否定断言(negative lookahead)

ES2018 引入后行断言,支持了后行断言(lookbehind)后行否定断言(negative lookbehind)

  • 先行断言 指的是,x 只有在 y 前面才匹配,必须写成 /x(?=y)/。比如,只匹配百分号之前的数字,要写成 /\d+(?=%)/
  • 先行否定断言 指的是,x 只有不在 y 前面才匹配,必须写成 /x(?!y)/。比如,只匹配不在百分号之前的数字,要写成 /\d+(?!%)/
  • 后行断言 正好与 先行断言 相反,x 只有在 y 后面才匹配,必须写成 /(?<=y)x/。比如,只匹配美元符号之后的数字,要写成 /(?<=$)\d+/
  • 后行否定断言 则与 先行否定断言 相反,x 只有不在 y 后面才匹配,必须写成/(?<!y)x/。比如,只匹配不在美元符号后面的数字,要写成/(?<!$)\d+/

Unicode 属性类

ES2018 引入了 Unicode 属性类,允许使用\p{...}\P{...}\P\p 的否定形式)代表一类 Unicode 字符,匹配满足条件的所有字符。

const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test("π"); // true

\p{Script=Greek} 表示匹配一个希腊文字母,所以匹配 π 成功

Unicode 属性类的标准形式,需要同时指定属性名和属性值。

\p{UnicodePropertyName=UnicodePropertyValue}

具名组匹配

正则表达式使用圆括号进行组匹配。

const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;

const matchObj = RE_DATE.exec("2000-12-31");
const year = matchObj[1]; // 2000
const month = matchObj[2]; // 12
const day = matchObj[3]; // 31

组匹配有两个问题:

  1. 每一组的匹配含义不容易看出来
  2. 只能用数字序号(比如 matchObj[1])进行索引

ES2018 引入了具名组匹配(Named Capture Groups),允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用。

const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;

const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // "1999"
const month = matchObj.groups.month; // "12"
const day = matchObj.groups.day; // "31"

d 修饰符:正则匹配索引

String.prototype.matchAll()

ES2020 增加了 String.prototype.matchAll() 方法,可以一次性取出所有匹配。不过,它返回的是一个遍历器(Iterator),而不是数组。

const string = 'test1test2test3';
const regex = /t(e)(st(\d?))/g;

for (const match of string.matchAll(regex)) {
  console.log(match);
}
// ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"]
// ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"]
// ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"]

相对于返回数组,返回遍历器的好处在于,如果匹配结果是一个很大的数组,那么遍历器比较节省资源。

遍历器转为数组是非常简单的,使用 ...运算符Array.from() 方法就可以了。