JavaScript¶
JavaScript has a rich built-in regex implementation with unique features and some surprising quirks.
Core Methods¶
// ── test — returns boolean ──────────────────────────────────
/^\d+$/.test("123") // true
/^\d+$/.test("abc") // false
// ── exec — returns match object (advances lastIndex with /g) ──
const re = /\d+/g;
let m;
while ((m = re.exec("12 and 34")) !== null) {
console.log(m[0], m.index); // "12" 0, "34" 7
}
// ── match — all matches with /g, first match object without ──
"12 and 34".match(/\d+/g) // ["12", "34"]
"12 and 34".match(/(\d+)/) // ["12", "12", index:0, groups:undefined]
// ── matchAll (ES2020) — iterator of all match objects ────────
const matches = [..."12 and 34".matchAll(/(\d+)/g)];
// Each element is a full match object with groups
// ── replace — first match (or all with /g) ──────────────────
"hello world".replace(/\w+/g, s => s.toUpperCase()); // "HELLO WORLD"
// ── replaceAll (ES2021) ──────────────────────────────────────
"a-b-c".replaceAll("-", "_"); // "a_b_c" (string literal only)
// ── split ───────────────────────────────────────────────────
"one1two2three".split(/\d/) // ["one", "two", "three"]
"one1two2".split(/(\d)/) // ["one","1","two","2",""] — captured delimiters included
The lastIndex Trap¶
// /g flag with exec mutates lastIndex — reuse without resetting is a bug
const re = /\d+/g;
re.exec("12 34"); // ["12"] — lastIndex = 2
re.exec("12 34"); // ["34"] — lastIndex = 5
re.exec("12 34"); // null — resets lastIndex to 0
re.exec("12 34"); // ["12"] — starts over!
// Fix: always reset or use matchAll
re.lastIndex = 0;
// Or: use a fresh regex for one-shot operations
match() with /g drops group captures
"foo bar".match(/(\w+)/g) returns ["foo", "bar"] — groups are lost.
Use matchAll() to get match objects with groups in global mode.
Named Captures (ES2018)¶
const { groups } = "2024-07-04".match(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
);
// groups.year = "2024", groups.month = "07", groups.day = "04"
// Named groups in replacement string
"2024-07-04".replace(
/(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/,
'$<d>/$<m>/$<y>'
); // "04/07/2024"
Sticky Flag /y¶
// /y only matches at re.lastIndex — no scanning
const re = /\d+/y;
re.lastIndex = 3;
re.exec("abc123") // ["123"] — matched at position 3
re.lastIndex = 0;
re.exec("abc123") // null — no digits at position 0
Useful for hand-rolled tokenizers that need to enforce contiguous, non-scanning matching.
Indices Flag /d (ES2022)¶
const m = "2024-07-04".match(/(?<year>\d{4})-(?<month>\d{2})/d);
m.indices[0] // [0, 7] — full match start/end
m.indices.groups.year // [0, 4] — named group start/end
Constructing Regex Dynamically¶
// new RegExp takes a string — backslashes must be doubled
const pattern = "\\d{4}"; // string contains: \d{4}
const re = new RegExp(pattern, "g"); // regex: /\d{4}/g
// Escaping user input for use in dynamic regex
function escapeRegex(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
const userSearch = "price (USD)";
const re = new RegExp(escapeRegex(userSearch), "gi");
Version Compatibility¶
| Feature | ES version | Notes |
|---|---|---|
Named captures (?<name>…) |
ES2018 | Also \k<name> backreference |
Lookbehind (?<=…) (?<!…) |
ES2018 | Not available in older engines |
s dotall flag |
ES2018 | . matches \n |
matchAll() |
ES2020 | |
d indices flag |
ES2022 | |
v UnicodeSets flag |
ES2024 | Set operations in [] |
/u Unicode flag |
ES2015 | Required for \p{} and astral chars |
JS-Specific Gotchas¶
/regex/literals compile at parse time — prefer them overnew RegExp()when the pattern is static.- No possessive quantifiers or atomic groups — restructure patterns instead.
String.prototype.search()always resetslastIndexto 0.split()with a capturing group includes the captured text in the result array (intentional, but surprising).