ES提案:String.prototype.matchAll

1,528 阅读2分钟

由Jordan Harband发起的String.prototype.matchAll,在今年三月的TC39会议中被收录到草案中。本文介绍将这个方法解决了什么问题。

JS如何通过正则获取字符串中所有匹配项

先看下现在我们是怎么通过正则去获取字符串中所有匹配项的。

String.prototype.match

我们可以使用String.prototype.match配合正则使用g标志,可以匹配到所有匹配项,但并不能得到捕获组。例如:

var regex = /t(e)(st(\d?))/g;
var string = 'test1test2';

string.match(regex) // ["test1", "test2"] 

在上述例子中,在结果中只能得到完全匹配内容,如"test1","test2",但并不能拿到捕获组,如

  • "e"(被(e)捕获)
  • "st1"(被(st(\d?))捕获)
  • "1"(被(\d?)捕获)

也没有匹配结果在原始字符串中的索引值。这样的匹配结果并不尽如人意。

RegExp.prototype.exec

如果想要获取所有匹配项及捕获组,在之前会使用RegExp.prototype.exec方法来实现。如:

var regex = /t(e)(st(\d?))/g;
var string = 'test1test2';

var matches = [];
var match;
while (match = regex.exec(string)) {
	matches.push(match);
}
// matches => [ 
//    ["test1", "e", "st1", "1", index: 0, input: "test1test2", groups: undefined], 
//    ["test2", "e", "st2", "2", index: 5, input: "test1test2", groups: undefined]
// ]

可以看到,通过RegExp.prototype.exec配合使用g标志,while遍历就可以获取到所有匹配项及捕获组。但这样显然很麻烦。而且如果没有g标志,就会变成死循环。另外如果操作regex.lastIndex也会出现问题:

var regex = /t(e)(st(\d?))/g;
var string = 'test1test2';
regex.lastIndex = 0;
regex.exec(string) // ["test2", "e", "st2", "2", index: 5, input: "test1test2", groups: undefined] 此时就不是从test1开始遍历了。

String.prototype.replace

使用一些小技巧,你还可以通过String.prototype.replace来实现。如:

var regex = /t(e)(st(\d?))/g;
var string = 'test1test2';
var matches = [];
string.replace(regex, function () {
	var match = Array.prototype.slice.call(arguments, 0, -2);
	match.input = arguments[arguments.length - 1];
	match.index = arguments[arguments.length - 2];
	matches.push(match);
});
// matches => [ 
//    ["test1", "e", "st1", "1", input: "test1test2", index: 0], 
//    ["test2", "e", "st2", "2", input: "test1test2", index: 5]
// ]

上述例子中我们可以得到匹配结果、捕获组及其索引。但这个显然滥用了replace,可读性也够呛。

新方法:String.prototype.matchAll

String.prototype.matchAll则完全解决了上面几种方案的各种问题。通过这个方法配合正则,你可以很容易的得到所有匹配项及其捕获组。需要注意的是该方法得到结果是一个迭代器。你可以配合for...of遍历,或者通过展开语法、Array.from将其转换成Array后再处理。

var regex = /t(e)(st(\d?))/g;
var string = 'test1test2';
string.matchAll(regex) // RegExpStringIterator {}

[...string.matchAll(regex)]
Array.from(string.matchAll(regex))
// [[ ["test1", "e", "st1", "1", index: 0, input: "test1test2", groups: undefined]],
//  ["test2", "e", "st2", "2", index: 5, input: "test1test2", groups: undefined]]

for (const match of string.matchAll(regex)) {
  console.log(`Found ${match} start=${match.index} end=${match.index + match[0].length}.`);
}
// Found test1,e,st1,1 start=0 end=5.
// Found test2,e,st2,2 start=5 end=10.