String.prototype.matchAll()
The method that closed the gap: match with /g gives every match but drops the groups, and without /g keeps the groups but finds only one. This gives you both.
Demo
The demo spreads the iterator and takes m[0], the whole match, so the output is comparable with the match page — but each m is a full match object, so m[1] would be the first capture group and m.index its position. Compare the third case directly with match: where match returns NULL and throws on .length, matchAll returns an empty iterator that spreads to an empty array. That alone removes the most common regex bug in JavaScript.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| regexp | RegExp | yes | The pattern, which must carry the g flag — a non-global regex is a TypeError, exactly as with replaceAll. |
Return value
Iterator — An ITERATOR of full match objects, each with capture groups, index and input. Not an array — spread it or loop it. Empty rather than null when nothing matches.
Common patterns
const pairs = [...s.matchAll(/(\w)=(\d)/g)].map(m => [m[1], m[2]]);
for (const m of s.matchAll(/(?<key>\w+)=(?<value>\d+)/g)) { console.log(m.groups.key, m.groups.value); }
const positions = [...s.matchAll(re)].map(m => m.index);
Examples
Pitfalls
'a1'.matchAll(/\d/g).length
[...'a1'.matchAll(/\d/g)].length
const it = s.matchAll(re); [...it].length; [...it].length
const all = [...s.matchAll(re)];
[...'a1'.matchAll(/\d/)]
[...'a1'.matchAll(/\d/g)]
while ((m = /a*/g.exec(s)) !== null) { }
for (const m of s.matchAll(/a*/g)) { }
When to use
- Every match together with its capture groups
- Named groups across multiple matches
- The index of each match in a single pass
- Anywhere a while-exec loop would otherwise appear
- You only want the whole matches → match with g is shorter
- You only need a boolean → RegExp.test
- The pattern is literal text → includes, indexOf or split
- Targeting runtimes older than 2020 → an exec loop, carefully
Notes
FAQ
So it can be lazy. A pattern over a very large string need not build every match up front, and for...of can stop early. Spreading it into an array when you want one is a single extra character of syntax.
for (const m of s.matchAll(re)) { if (done) break; }