RegExp的exec()函数和String的match()函数有什么区别?

148

如果我运行这个:

/([^\/]+)+/g.exec('/a/b/c/d');

我得到了这个:

["a", "a"]

但是如果我运行这个:

'/a/b/c/d'.match(/([^\/]+)+/g);

然后我得到了这个预期结果:

["a", "b", "c", "d"]

有什么不同之处?


4
你可以使用 exec 循环获取所有的子选择。 - zzzzBov
3
请注意,第二个+不必要,因为match已经返回了所有的子表达式。 .exec每次只返回一个,因此也不需要那个+ - pimvdb
4
此外,像两个加号这样的嵌套量词应该非常小心地使用,因为它们很容易导致灾难性回溯。 - Marius Schulz
1
@MariusSchulz 感谢提供链接。这让我了解了所谓的占有量词和原子组合。非常好的东西值得理解。 - Justin Warkentin
7个回答

133

exec与全局正则表达式一起使用时,应该放在循环中使用,因为它仍然会检索所有匹配的子表达式。所以:

var re = /[^\/]+/g;
var match;

while (match = re.exec('/a/b/c/d')) {
    // match is now the next match, in array form.
}

// No more matches.

String.match可以为您执行此操作,并且会丢弃捕获的组。


45
我对此回答有所补充,不应该在while条件中放置正则表达式字面量,像这样while(match = /[^\/]+/g.exec('/a/b/c/d'),否则会创建一个无限循环!根据MDN上明确的说明https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec。 - yeyo
9
更具体地说,它必须是同一个正则表达式对象,而字面量无法实现这一点。 - Ry-
1
@Ry- 我认为应该注意到这种行为是在ES5中引入的。在ES5之前,new RegExp("pattern")/pattern/有不同的含义。 - Robert

83

一张图片胜过千言万语...

re_once = /([a-z])([A-Z])/
re_glob = /([a-z])([A-Z])/g

st = "aAbBcC"

console.log("match once="+ st.match(re_once)+ "  match glob="+ st.match(re_glob))
console.log("exec once="+ re_once.exec(st) + "   exec glob="+ re_glob.exec(st))
console.log("exec once="+ re_once.exec(st) + "   exec glob="+ re_glob.exec(st))
console.log("exec once="+ re_once.exec(st) + "   exec glob="+ re_glob.exec(st))

看到区别了吗?

注意:为了强调,需要注意捕获组(例如:a,A)是在匹配的模式(例如:aA)之后返回的,而不仅仅是匹配的模式本身。


32
如果你的正则表达式是全局的并且要进行捕获,那么你必须使用exec。Match无法返回所有的捕获。
当只需要匹配(不进行捕获)时,Match非常好用。你运行一次它会给出所有匹配的数组。(尽管如果正则表达式不是全局的,那么match将显示匹配后跟随捕获)
当你进行捕获时,Exec是你要使用的,每次执行它都会给出匹配结果,然后是捕获。 (当正则表达式不是全局时,match会以完整的匹配结果跟随捕获的方式来运行)
另一个使用Exec的方法是获取匹配的索引或位置。当你有一个变量作为正则表达式时,你可以使用.lastIndex并获取匹配的位置。正则表达式对象有.lastIndex,而正则表达式对象是你对其执行.exec的对象。Dot match是在字符串上完成的,你将无法使用正则表达式对象点lastIndex。
字符串有match函数,它接收一个正则表达式。而正则表达式有exec函数,它接收一个字符串。
你可以多次运行exec,但只需运行一次match。
当不进行捕获时最好使用match,而当进行捕获时,你可以使用更强大的exec来获取捕获结果,但如果你在进行捕获时使用了match,请注意,它仅在正则表达式不是全局时显示捕获结果,但在正则表达式是全局时不会显示捕获结果。
> "azb".match(/a(z)b/);
[ "azb", "z" ]

> "azb".match(/a(z)b/g);
[ "azb" ]
>

另外一件事是,如果您使用exec,请注意它是在正则表达式上调用的,如果您为正则表达式使用了变量,则具有更大的控制力。

当您不使用正则表达式的变量时,无法获取匹配项,因此在使用exec时请使用正则表达式的变量。

> /./g.exec("abc")
[ "a" ]
> /./g.exec("abc")
[ "a" ]
> /./g.exec("abc")
[ "a" ]
>
> /[a-c]/g.exec("abc")
[ "a" ]
> /[a-c]/g.exec("abc")
[ "a" ]
>

> var r=/[a-c]/g
> r.exec("abc")
[ "a" ]
> r.exec("abc")
[ "b" ]
> r.exec("abc")
[ "c" ]
> r.exec("abc")
null
>

使用 exec 方法,您可以获取匹配项的“索引”

> var r=/T/g
> r.exec("qTqqqTqqTq");
[ "T" ]
> r.lastIndex
2
> r.exec("qTqqqTqqTq");
[ "T" ]
> r.lastIndex
6
> r.exec("qTqqqTqqTq");
[ "T" ]
> r.lastIndex
9
> r.exec("qTqqqTqqTq");
null
> r.lastIndex
0
>

因此,如果您需要索引或捕获,则使用exec(请记住,如您所见,对于“index”,它给出的“index”实际上是第n个出现,它从1开始计数。因此,您可以通过减去1来得出正确的索引。如您所见,对于未找到的情况,它会给出0 - lastIndex为0)。

如果您想要进行伸展匹配,可以在捕获时使用它,但不适用于正则表达式为全局时,并且当您为其执行时,数组的内容并不是所有匹配项,而是完整的匹配项以及其后的捕获内容。


是的,了解r.lastIndex的工作方式是理解execmatch之间差异的关键因素。 - runsun
如果你的正则表达式是全局的,并且你正在捕获,那么你必须使用exec。Match不会返回所有捕获内容。我在控制台上得到了它。只需复制/粘贴“a,b,c,aa,bb,cc”.match(/(\w+)/g); Opera,Firefox。 - MrHIDEn
@MrHIDEn 我不建议你在错误的引用中使用你所使用的语言。重要的是显示出来的内容和我们能够看到的内容,背后是否有缓存也不重要。虽然我已经有一段时间没有研究这个问题了,但它并没有显示所有的捕获。即使你使用了你的例子 "a,b,c,aa,bb,cc".match(/(\w+)/g),它显示了所有的匹配项,而恰好你捕获了每一个匹配项,所以如果它要显示所有的捕获,它看起来会完全相同(续)。 - barlop
所以也许你认为它正在显示捕获,但实际上它并不是,它正在显示匹配。 - barlop
@MrHIDEn 是的,我一注意到您突然更改了长查找模式并在您回复之前删除了评论,我就立刻将那条评论删除了。但是现在你同意我的答案了。 - barlop
显示剩余3条评论

32
/regex/.exec() 方法只返回第一个匹配项,而使用正则表达式的 g 标记时 "string".match() 方法将返回所有匹配项。
请参考:exec, match

7

.match() 函数 str.match(regexp) 的功能如下:

  • 如果存在匹配,则返回:
    • 如果在正则表达式中使用了 g 标志:它将返回所有子字符串(忽略捕获组)
    • 如果在正则表达式中未使用 g 标志:它将返回与 regexp.exec(str) 相同的结果
  • 如果不存在匹配,则返回:
    • null

使用 .match() 的示例,其中使用了 g 标志:

var str = "qqqABApppabacccaba";
var e1, e2, e3, e4, e5;
e1 = str.match(/nop/g); //null
e2 = str.match(/no(p)/g); //null
e3 = str.match(/aba/g); //["aba", "aba"]
e4 = str.match(/aba/gi); //["ABA", "aba", "aba"]
e5 = str.match(/(ab)a/g); //["aba", "aba"] ignoring capture groups as it is using the g flag

.match()函数不带g标记相当于.exec()函数:

e1=JSON.stringify(str.match(/nop/))===JSON.stringify(/nop/.exec(str)); //true
//e2 ... e4 //true
e5=JSON.stringify(str.match(/(ab)a/))===JSON.stringify(/(ab)a/.exec(str)); //true

.exec()函数regexp.exec(str)的作用:

  • 如果能匹配成功,将返回:
    • 如果正则表达式使用了g标记,那么每次调用时都将返回下一个N个匹配项和对应的捕获组结果[N_MatchedStr, N_Captured1, N_Captured2, ...]。请注意:重要提示:如果正则表达式对象没有存储在变量中(需要是同一个对象),它不会进入下一个匹配项。
    • 如果正则表达式没有使用g标记,它将返回与第一次调用并且只执行一次且使用了g标记的相同结果。
  • 如果无法匹配成功,将返回:
    • null

.exec()的示例(存储的正则表达式+使用g标记=每次调用时都会改变):

var str = "qqqABApppabacccaba";
var myexec, rgxp = /(ab)a/gi;

myexec = rgxp.exec(str);
console.log(myexec); //["ABA", "AB"]
myexec = rgxp.exec(str);
console.log(myexec); //["aba", "ab"]
myexec = rgxp.exec(str);
console.log(myexec); //["aba", "ab"]
myexec = rgxp.exec(str);
console.log(myexec); //null

//But in this case you should use a loop:
var mtch, myRe = /(ab)a/gi;
while(mtch = myRe.exec(str)){ //infinite looping with direct regexps: /(ab)a/gi.exec()
    console.log("elm: "+mtch[0]+" all: "+mtch+" indx: "+myRe.lastIndex);
    //1st iteration = elm: "ABA" all: ["ABA", "AB"] indx: 6
    //2nd iteration = elm: "aba" all: ["aba", "ab"] indx: 12
    //3rd iteration = elm: "aba" all: ["aba", "ab"] indx: 18
}

.exec()方法在每次调用时不会改变的示例:

var str = "qqqABApppabacccaba", myexec, myexec2;

//doesn't go into the next one because no g flag
var rgxp = /(a)(ba)/;
myexec = rgxp.exec(str);
console.log(myexec); //["aba", "a", "ba"]
myexec = rgxp.exec(str);
console.log(myexec); //["aba", "a", "ba"]
//... ["aba", "a", "ba"]

//doesn't go into the next one because direct regexp
myexec2 = /(ab)a/gi.exec(str);
console.log(myexec2); //["ABA", "AB"]
myexec2 = /(ab)a/gi.exec(str);
console.log(myexec2); //["ABA", "AB"]
//... ["ABA", "AB"]

0

有时候,regex.exec() 的执行时间比 string.match() 要长得多。

值得一提的是,如果 string.match() 和 regex.exec() 的结果相同(例如不使用 \g 标志),则 regex.exec() 的执行时间将在 string.match() 的 2 到 30 倍之间:

因此,在这种情况下,只有当您需要全局正则表达式(即执行多次)时,才应该使用 "new RegExp().exec()" 的方法。


很难说这是真的... - barsdeveloper

0
两者都是做相同的事情,不同之处在于一个是独立的表达式,第二个是字符串的方法。 如果您想要、需要、必须对给定的表达式执行相同的操作,那么exec更好,因为您不需要创建新的表达式。表达式的构造函数不会运行两次。Match可以在字符串上进行链式操作,例如:
match chain:
some_string.replace(expr).match(expr); //etc...

With exec:
let exec = new RegExp(regexp );
for(for expression ){
   exec.(string );
   //other body of FOR
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接