If a regular expression has the /g flag, you call .exec() multiple times to get all matches. After the last match, it returns null. Before that, it returns a match object for each match. Such an object contains captured substrings and more.

In the following example, we collect all captures of group 1 in the Array matches:

You can use a trick to collect captures via .replace(): We use a function to compute the replacement values. That function receives all capture information. However, instead of computing replacement values, it collects the data it is interested in, in the Array matches:

You can split a string and use a regular expression to specify the separator. If that regular expression contains at least one capture group then .split() returns an Array in which the substrings are interleaved with whatever the first group captures:

They only work if /g is set. Sometimes we receive a regular expression from somewhere else, e.g. via a parameter. Then we have to check that this flag is set if we want to be sure that all matches are found.

In order to keep track of progress, all approaches (except .match()) change the regular expression: property .lastIndex records where the previous match ended. This makes using the same regular expression at multiple locations risky. And while it’s generally not recommended, it’s a shame that you can’t inline the regular expression when using .exec() multiple times (because the regular expression is reset for each invocation):

···
// Doesn’t work:const match = /abc/ug.exec(str);
···

Due to property .lastIndex determining where matching continues, it must always be zero when we start collecting matches. But at least .exec() and friends reset it to zero after the last match. This is what happens if it isn’t zero: