JavaScript Regular Expressions (RegEx)
1. RegExp Literal (/pattern/) and Constructor Syntax
| Syntax | Form | Description | Example |
|---|---|---|---|
| Literal Syntax | /pattern/flags |
Most common; compiled at parse time; cannot use variables in pattern | /hello/i |
| Constructor | new RegExp(pattern, flags) |
Dynamic patterns; compiled at runtime; pattern is string (escape backslashes!) | new RegExp('\\d+', 'g') |
| Constructor (no new) | RegExp(pattern, flags) |
Same as with new; returns RegExp instance |
RegExp('test') |
| From RegExp | new RegExp(regexp, flags) |
Clone with optional new flags; ES2015+ | new RegExp(/test/i, 'g') |
| Property | Type | Description | Example |
|---|---|---|---|
| source | String | Pattern text (without delimiters and flags) | /abc/i.source // "abc" |
| flags | String | All flags as string; alphabetically sorted | /a/gi.flags // "gi" |
| global | Boolean | Has g flag |
/a/g.global // true |
| ignoreCase | Boolean | Has i flag |
/a/i.ignoreCase // true |
| multiline | Boolean | Has m flag |
/a/m.multiline // true |
| dotAll | Boolean | Has s flag (. matches newlines) |
/a/s.dotAll // true |
| unicode | Boolean | Has u flag |
/a/u.unicode // true |
| sticky | Boolean | Has y flag |
/a/y.sticky // true |
| lastIndex | Number | Start position for next match; used with g or y flags |
regex.lastIndex = 0 |
Example: RegExp creation and properties
// Literal syntax (preferred)
const literal = /hello/i;
const pattern = /\d{3}-\d{4}/;
// Constructor - dynamic patterns
const word = 'test';
const dynamic = new RegExp(word, 'gi');
// Matches 'test' case-insensitively, globally
// Constructor - escape backslashes!
const wrong = new RegExp('\d+'); // ❌ \d becomes 'd'
const correct = new RegExp('\\d+'); // ✓ \\d becomes \d
// Or use String.raw
const regex = new RegExp(String.raw`\d+`);
// Clone with new flags
const original = /test/i;
const clone = new RegExp(original, 'g');
// Pattern: "test", flags: "g" (not "gi")
// Properties
const re = /hello/gi;
re.source; // "hello"
re.flags; // "gi"
re.global; // true
re.ignoreCase; // true
re.multiline; // false
// lastIndex (stateful with g or y flag)
const globalRe = /test/g;
globalRe.lastIndex; // 0 (initial)
globalRe.exec('test test');
globalRe.lastIndex; // 4 (after first match)
// Reset lastIndex
globalRe.lastIndex = 0;
// Create from string (user input)
function createRegex(userPattern, options = {}) {
const flags =
(options.global ? 'g' : '') +
(options.ignoreCase ? 'i' : '') +
(options.multiline ? 'm' : '');
try {
return new RegExp(userPattern, flags);
} catch (e) {
console.error('Invalid regex:', e.message);
return null;
}
}
// Escape special characters for literal matching
function escapeRegex(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\
// Literal syntax (preferred)
const literal = /hello/i;
const pattern = /\d{3}-\d{4}/;
// Constructor - dynamic patterns
const word = 'test';
const dynamic = new RegExp(word, 'gi');
// Matches 'test' case-insensitively, globally
// Constructor - escape backslashes!
const wrong = new RegExp('\d+'); // ❌ \d becomes 'd'
const correct = new RegExp('\\d+'); // ✓ \\d becomes \d
// Or use String.raw
const regex = new RegExp(String.raw`\d+`);
// Clone with new flags
const original = /test/i;
const clone = new RegExp(original, 'g');
// Pattern: "test", flags: "g" (not "gi")
// Properties
const re = /hello/gi;
re.source; // "hello"
re.flags; // "gi"
re.global; // true
re.ignoreCase; // true
re.multiline; // false
// lastIndex (stateful with g or y flag)
const globalRe = /test/g;
globalRe.lastIndex; // 0 (initial)
globalRe.exec('test test');
globalRe.lastIndex; // 4 (after first match)
// Reset lastIndex
globalRe.lastIndex = 0;
// Create from string (user input)
function createRegex(userPattern, options = {}) {
const flags =
(options.global ? 'g' : '') +
(options.ignoreCase ? 'i' : '') +
(options.multiline ? 'm' : '');
try {
return new RegExp(userPattern, flags);
} catch (e) {
console.error('Invalid regex:', e.message);
return null;
}
}
// Escape special characters for literal matching
function escapeRegex(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
const userInput = 'hello.world';
const escaped = escapeRegex(userInput); // "hello\\.world"
const safe = new RegExp(escaped); // matches literal "hello.world"
#x26;");
}
const userInput = 'hello.world';
const escaped = escapeRegex(userInput); // "hello\\.world"
const safe = new RegExp(escaped); // matches literal "hello.world"
Note: Use literals
/pattern/flags for static patterns (better performance);
constructor for dynamic patterns. Remember to double-escape backslashes in strings: '\\d' not
'\d'.
2. RegExp Flags (g, i, m, s, u, y) and Global Modifiers
| Flag | Name | Description | Effect |
|---|---|---|---|
| g | Global | Find all matches, not just first; affects exec(), test(), match(), replace() | Multiple matches |
| i | Ignore Case | Case-insensitive matching; [a-z] matches both cases | /a/i matches 'A' and 'a' |
| m | Multiline | ^ and $ match line boundaries, not just string start/end | ^word matches after \n |
| s ES2018 | DotAll | . (dot) matches newlines; normally dot excludes \n \r \u2028 \u2029 | /.+/s matches across lines |
| u ES2015 | Unicode | Full Unicode support; astral characters, Unicode escapes, properties | \u{1F600} and \p{...} |
| y ES2015 | Sticky | Match must start at lastIndex; no skipping ahead | For tokenizers/parsers |
| d ES2022 | Indices | Capture group indices in .indices property of match result | match.indices[0] |
Example: Flag behaviors
const str = 'Hello World\nHello Again';
// g - global (all matches)
str.match(/hello/i); // ["Hello"] (first only)
str.match(/hello/gi); // ["Hello", "Hello"] (all)
/\d+/g.test('123'); // true
/\d+/g.test('123'); // false (lastIndex moved!)
// i - ignore case
/hello/.test('HELLO'); // false
/hello/i.test('HELLO'); // true
// m - multiline (^ $ match line boundaries)
const multi = 'line1\nline2\nline3';
multi.match(/^line/g); // ["line"] (only first)
multi.match(/^line/gm); // ["line", "line", "line"] (each line)
/world$/.test('hello\nworld'); // true (end of string)
/world$/m.test('world\nhello'); // true (end of line)
// s - dotAll (. matches newlines)
/.+/.test('hello\nworld'); // false (. stops at \n)
/.+/s.test('hello\nworld'); // true (. matches \n)
'a\nb'.match(/a.b/); // null
'a\nb'.match(/a.b/s); // ["a\nb"]
// u - unicode (proper astral character handling)
'😀'.match(/./); // ["�"] ❌ (high surrogate only)
'😀'.match(/./u); // ["😀"] ✓
// Unicode property escapes (requires u flag)
/\p{Emoji}/u.test('😀'); // true
/\p{Script=Greek}/u.test('α'); // true
/\p{Letter}/u.test('A'); // true
// Count code points, not code units
'😀'.length; // 2
'😀'.match(/./gu); // ["😀"] (1 match)
'😀'.match(/./g); // ["�","�"] (2 matches)
// y - sticky (match at exact position)
const sticky = /\d+/y;
sticky.lastIndex = 0;
sticky.exec('123 456'); // ["123"]
sticky.lastIndex; // 3
sticky.exec('123 456'); // null (position 3 is space)
sticky.lastIndex; // 0 (reset on failure)
// Without y, would skip ahead and match 456
const notSticky = /\d+/g;
notSticky.lastIndex = 3;
notSticky.exec('123 456'); // ["456"]
// d - indices (capture group positions)
const indicesRe = /(\d+)-(\d+)/d;
const match = indicesRe.exec('id: 123-456');
match[1]; // "123"
match[2]; // "456"
match.indices[1]; // [4, 7] (position of "123")
match.indices[2]; // [8, 11] (position of "456")
// Combine flags
const combined = /pattern/gimsu;
combined.flags; // "gimsu" (alphabetically sorted)
Warning: Global
g flag makes RegExp stateful
(lastIndex). Reset with regex.lastIndex = 0 or use string methods. Always use u flag
for Unicode text!
3. Pattern Matching Methods (test, exec, match, matchAll)
| Method | Syntax | Description | Returns |
|---|---|---|---|
| test() | regex.test(str) |
Check if pattern matches; fastest for existence check | Boolean |
| exec() | regex.exec(str) |
Get match details with capture groups; use in loop with /g flag | Array | null |
| match() | str.match(regex) |
Without /g: like exec(); with /g: array of all matches (no details) | Array | null |
| matchAll() ES2020 | str.matchAll(regex) |
Iterator of all matches with full details; regex must have /g | Iterator |
Example: Pattern matching methods
const str = 'The year 2025 and 2026';
// test() - simple existence check
/\d+/.test(str); // true
/cat/.test(str); // false
// Use for validation
function isEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// exec() - detailed match info
const yearRe = /(\d{4})/;
const match = yearRe.exec(str);
match[0]; // "2025" (full match)
match[1]; // "2025" (capture group 1)
match.index; // 9 (position)
match.input; // original string
// exec() with /g flag (iterate)
const yearReGlobal = /(\d{4})/g;
let m;
while ((m = yearReGlobal.exec(str)) !== null) {
console.log(m[1], 'at', m.index);
// "2025" at 9
// "2026" at 18
}
// match() without /g (same as exec)
str.match(/(\d{4})/);
// ["2025", "2025", index: 9, input: "...", groups: undefined]
// match() with /g (all matches, no details)
str.match(/\d{4}/g); // ["2025", "2026"]
str.match(/\d+/g); // ["2025", "2026"]
// matchAll() - best of both (ES2020+)
const years = str.matchAll(/(\d{4})/g);
for (const match of years) {
console.log(match[1], 'at', match.index);
// "2025" at 9
// "2026" at 18
}
// Convert to array
const allMatches = [...str.matchAll(/(\d{4})/g)];
// Named capture groups
const phoneRe = /(?<area>\d{3})-(?<exchange>\d{3})-(?<number>\d{4})/;
const phone = '555-123-4567';
const phoneMatch = phone.match(phoneRe);
phoneMatch[1]; // "555"
phoneMatch.groups.area; // "555"
phoneMatch.groups.exchange; // "123"
phoneMatch.groups.number; // "4567"
// Multiple patterns
const email = 'user@example.com';
const emailRe = /^(?<user>[^@]+)@(?<domain>[^@]+)$/;
const emailMatch = email.match(emailRe);
emailMatch.groups.user; // "user"
emailMatch.groups.domain; // "example.com"
// No match returns null
'abc'.match(/\d+/); // null
/\d+/.test('abc'); // false
/\d+/.exec('abc'); // null
// Global test() gotcha (stateful!)
const re = /test/g;
re.test('test test'); // true (first call)
re.test('test test'); // true (second call)
re.test('test test'); // false! (lastIndex past end)
re.lastIndex; // 0 (reset)
// Solution: don't reuse global regex for test()
function hasDigit(str) {
return /\d/.test(str); // no /g, always works
}
// Or reset lastIndex
function hasPattern(str, regex) {
regex.lastIndex = 0;
return regex.test(str);
}
Note: Use
test() for boolean checks; match() for simple extraction;
matchAll() for all matches with details. Avoid test() with global regex in loops
(stateful).
4. String RegExp Methods (replace, replaceAll, split, search)
| Method | Syntax | Description | Returns |
|---|---|---|---|
| replace() | str.replace(regex, newStr|fn) |
Replace first match (or all with /g); newStr can use $1, $2, etc. | String |
| replaceAll() ES2021 | str.replaceAll(str|regex, newStr|fn) |
Replace all occurrences; regex must have /g flag | String |
| split() | str.split(regex, limit) |
Split string by pattern; optional limit; capture groups included in result | Array |
| search() | str.search(regex) |
Find index of first match; ignores /g flag; like indexOf for regex | Index | -1 |
| Replacement Patterns (in replace newStr argument) | |||
|---|---|---|---|
| Pattern | Inserts | Example | Result |
| $$ | Literal $ | '$10'.replace(/\d+/, '$$&') |
"$&10" |
| $& | Entire match | 'cat'.replace(/\w+/, '[$&]') |
"[cat]" |
| $` | Text before match | 'a-b'.replace(/-/, '$`') |
"aab" |
| $' | Text after match | 'a-b'.replace(/-/, "$'") |
"abb" |
| $n | Capture group n (1-99) | 'John Doe'.replace(/(\w+) (\w+)/, '$2, $1') |
"Doe, John" |
| $<name> | Named capture group |
'2025-12-17'.replace(/(?<y>\d+)-(?<m>\d+)-(?<d>\d+)/, '$<m>/$<d>/$<y>')
|
"12/17/2025" |
Example: String RegExp methods
const str = 'Hello World, hello again';
// replace - first match
str.replace('hello', 'hi'); // "Hello World, hi again"
str.replace(/hello/, 'hi'); // "Hello World, hi again"
// replace - all matches (with /g)
str.replace(/hello/gi, 'hi'); // "hi World, hi again"
// replaceAll - simpler for strings
'aaa'.replace(/a/g, 'b'); // "bbb"
'aaa'.replaceAll('a', 'b'); // "bbb"
// Replacement patterns
'test'.replace(/test/, '[
const str = 'Hello World, hello again';
// replace - first match
str.replace('hello', 'hi'); // "Hello World, hi again"
str.replace(/hello/, 'hi'); // "Hello World, hi again"
// replace - all matches (with /g)
str.replace(/hello/gi, 'hi'); // "hi World, hi again"
// replaceAll - simpler for strings
'aaa'.replace(/a/g, 'b'); // "bbb"
'aaa'.replaceAll('a', 'b'); // "bbb"
// Replacement patterns
'test'.replace(/test/, '[$&]'); // "[test]"
'a-b'.replace(/-/, '($`|$\')'); // "a(a|b)b"
// Capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"
// Named groups
const date = '2025-12-17';
date.replace(
/(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/,
'$<month>/$<day>/$<year>'
); // "12/17/2025"
// Replace with function
str.replace(/\b\w+\b/g, match => match.toUpperCase());
// "HELLO WORLD, HELLO AGAIN"
// Function receives: match, p1, p2, ..., offset, string, groups
'1 plus 2 equals 3'.replace(/(\d+)/g, (match, p1, offset) => {
return parseInt(p1) * 2;
}); // "2 plus 4 equals 6"
// Advanced replacements
const text = 'Price: $10.50, Total: $20.00';
text.replace(/\$(\d+\.\d+)/g, (match, price) => {
return `$${(parseFloat(price) * 1.1).toFixed(2)}`;
}); // "Price: $11.55, Total: $22.00"
// split - by regex
'a1b2c3'.split(/\d/); // ["a", "b", "c", ""]
'one,two;three:four'.split(/[,;:]/);
// ["one", "two", "three", "four"]
// split - with limit
'a-b-c-d'.split(/-/, 2); // ["a", "b"]
// split - capture groups included in result
'a1b2c'.split(/(\d)/);
// ["a", "1", "b", "2", "c"]
// split - whitespace
' hello world '.split(/\s+/);
// ["", "hello", "world", ""]
' hello world '.trim().split(/\s+/);
// ["hello", "world"]
// search - find index
str.search(/world/i); // 6
str.search(/cat/); // -1
// search vs indexOf
'hello'.indexOf('l'); // 2 (first 'l')
'hello'.search(/l/); // 2 (first 'l')
// search with patterns
'abc123xyz'.search(/\d/); // 3 (first digit)
'test@email.com'.search(/@/); // 4
// Use cases
// 1. Format phone numbers
function formatPhone(phone) {
return phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
}
formatPhone('5551234567'); // "(555) 123-4567"
// 2. Sanitize HTML
function escapeHtml(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// 3. Slugify
function slugify(str) {
return str
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
}
slugify('Hello World!'); // "hello-world"
// 4. Extract data
const email = 'Contact: user@example.com';
const match = email.match(/[\w.]+@[\w.]+/);
const emailAddr = match ? match[0] : null;
#x26;]'); // "[test]"
'a-b'.replace(/-/, '(
JavaScript Regular Expressions (RegEx)
1. RegExp Literal (/pattern/) and Constructor Syntax
Syntax
Form
Description
Example
Literal Syntax
/pattern/flags
Most common; compiled at parse time; cannot use variables in pattern
/hello/i
Constructor
new RegExp(pattern, flags)
Dynamic patterns; compiled at runtime; pattern is string (escape backslashes!)
new RegExp('\\d+', 'g')
Constructor (no new)
RegExp(pattern, flags)
Same as with new; returns RegExp instance
RegExp('test')
From RegExp
new RegExp(regexp, flags)
Clone with optional new flags; ES2015+
new RegExp(/test/i, 'g')
Property
Type
Description
Example
source
String
Pattern text (without delimiters and flags)
/abc/i.source // "abc"
flags
String
All flags as string; alphabetically sorted
/a/gi.flags // "gi"
global
Boolean
Has g flag
/a/g.global // true
ignoreCase
Boolean
Has i flag
/a/i.ignoreCase // true
multiline
Boolean
Has m flag
/a/m.multiline // true
dotAll
Boolean
Has s flag (. matches newlines)
/a/s.dotAll // true
unicode
Boolean
Has u flag
/a/u.unicode // true
sticky
Boolean
Has y flag
/a/y.sticky // true
lastIndex
Number
Start position for next match; used with g or y flags
regex.lastIndex = 0
Example: RegExp creation and properties
// Literal syntax (preferred)
const literal = /hello/i;
const pattern = /\d{3}-\d{4}/;
// Constructor - dynamic patterns
const word = 'test';
const dynamic = new RegExp(word, 'gi');
// Matches 'test' case-insensitively, globally
// Constructor - escape backslashes!
const wrong = new RegExp('\d+'); // ❌ \d becomes 'd'
const correct = new RegExp('\\d+'); // ✓ \\d becomes \d
// Or use String.raw
const regex = new RegExp(String.raw`\d+`);
// Clone with new flags
const original = /test/i;
const clone = new RegExp(original, 'g');
// Pattern: "test", flags: "g" (not "gi")
// Properties
const re = /hello/gi;
re.source; // "hello"
re.flags; // "gi"
re.global; // true
re.ignoreCase; // true
re.multiline; // false
// lastIndex (stateful with g or y flag)
const globalRe = /test/g;
globalRe.lastIndex; // 0 (initial)
globalRe.exec('test test');
globalRe.lastIndex; // 4 (after first match)
// Reset lastIndex
globalRe.lastIndex = 0;
// Create from string (user input)
function createRegex(userPattern, options = {}) {
const flags =
(options.global ? 'g' : '') +
(options.ignoreCase ? 'i' : '') +
(options.multiline ? 'm' : '');
try {
return new RegExp(userPattern, flags);
} catch (e) {
console.error('Invalid regex:', e.message);
return null;
}
}
// Escape special characters for literal matching
function escapeRegex(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\
// Literal syntax (preferred)
const literal = /hello/i;
const pattern = /\d{3}-\d{4}/;
// Constructor - dynamic patterns
const word = 'test';
const dynamic = new RegExp(word, 'gi');
// Matches 'test' case-insensitively, globally
// Constructor - escape backslashes!
const wrong = new RegExp('\d+'); // ❌ \d becomes 'd'
const correct = new RegExp('\\d+'); // ✓ \\d becomes \d
// Or use String.raw
const regex = new RegExp(String.raw`\d+`);
// Clone with new flags
const original = /test/i;
const clone = new RegExp(original, 'g');
// Pattern: "test", flags: "g" (not "gi")
// Properties
const re = /hello/gi;
re.source; // "hello"
re.flags; // "gi"
re.global; // true
re.ignoreCase; // true
re.multiline; // false
// lastIndex (stateful with g or y flag)
const globalRe = /test/g;
globalRe.lastIndex; // 0 (initial)
globalRe.exec('test test');
globalRe.lastIndex; // 4 (after first match)
// Reset lastIndex
globalRe.lastIndex = 0;
// Create from string (user input)
function createRegex(userPattern, options = {}) {
const flags =
(options.global ? 'g' : '') +
(options.ignoreCase ? 'i' : '') +
(options.multiline ? 'm' : '');
try {
return new RegExp(userPattern, flags);
} catch (e) {
console.error('Invalid regex:', e.message);
return null;
}
}
// Escape special characters for literal matching
function escapeRegex(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
const userInput = 'hello.world';
const escaped = escapeRegex(userInput); // "hello\\.world"
const safe = new RegExp(escaped); // matches literal "hello.world"
#x26;");
}
const userInput = 'hello.world';
const escaped = escapeRegex(userInput); // "hello\\.world"
const safe = new RegExp(escaped); // matches literal "hello.world"
Note: Use literals /pattern/flags for static patterns (better performance);
constructor for dynamic patterns. Remember to double-escape backslashes in strings: '\\d' not
'\d'.
2. RegExp Flags (g, i, m, s, u, y) and Global Modifiers
Flag
Name
Description
Effect
g
Global
Find all matches, not just first; affects exec(), test(), match(), replace()
Multiple matches
i
Ignore Case
Case-insensitive matching; [a-z] matches both cases
/a/i matches 'A' and 'a'
m
Multiline
^ and $ match line boundaries, not just string start/end
^word matches after \n
s ES2018
DotAll
. (dot) matches newlines; normally dot excludes \n \r \u2028 \u2029
/.+/s matches across lines
u ES2015
Unicode
Full Unicode support; astral characters, Unicode escapes, properties
\u{1F600} and \p{...}
y ES2015
Sticky
Match must start at lastIndex; no skipping ahead
For tokenizers/parsers
d ES2022
Indices
Capture group indices in .indices property of match result
match.indices[0]
Example: Flag behaviors
const str = 'Hello World\nHello Again';
// g - global (all matches)
str.match(/hello/i); // ["Hello"] (first only)
str.match(/hello/gi); // ["Hello", "Hello"] (all)
/\d+/g.test('123'); // true
/\d+/g.test('123'); // false (lastIndex moved!)
// i - ignore case
/hello/.test('HELLO'); // false
/hello/i.test('HELLO'); // true
// m - multiline (^ $ match line boundaries)
const multi = 'line1\nline2\nline3';
multi.match(/^line/g); // ["line"] (only first)
multi.match(/^line/gm); // ["line", "line", "line"] (each line)
/world$/.test('hello\nworld'); // true (end of string)
/world$/m.test('world\nhello'); // true (end of line)
// s - dotAll (. matches newlines)
/.+/.test('hello\nworld'); // false (. stops at \n)
/.+/s.test('hello\nworld'); // true (. matches \n)
'a\nb'.match(/a.b/); // null
'a\nb'.match(/a.b/s); // ["a\nb"]
// u - unicode (proper astral character handling)
'😀'.match(/./); // ["�"] ❌ (high surrogate only)
'😀'.match(/./u); // ["😀"] ✓
// Unicode property escapes (requires u flag)
/\p{Emoji}/u.test('😀'); // true
/\p{Script=Greek}/u.test('α'); // true
/\p{Letter}/u.test('A'); // true
// Count code points, not code units
'😀'.length; // 2
'😀'.match(/./gu); // ["😀"] (1 match)
'😀'.match(/./g); // ["�","�"] (2 matches)
// y - sticky (match at exact position)
const sticky = /\d+/y;
sticky.lastIndex = 0;
sticky.exec('123 456'); // ["123"]
sticky.lastIndex; // 3
sticky.exec('123 456'); // null (position 3 is space)
sticky.lastIndex; // 0 (reset on failure)
// Without y, would skip ahead and match 456
const notSticky = /\d+/g;
notSticky.lastIndex = 3;
notSticky.exec('123 456'); // ["456"]
// d - indices (capture group positions)
const indicesRe = /(\d+)-(\d+)/d;
const match = indicesRe.exec('id: 123-456');
match[1]; // "123"
match[2]; // "456"
match.indices[1]; // [4, 7] (position of "123")
match.indices[2]; // [8, 11] (position of "456")
// Combine flags
const combined = /pattern/gimsu;
combined.flags; // "gimsu" (alphabetically sorted)
Warning: Global g flag makes RegExp stateful
(lastIndex). Reset with regex.lastIndex = 0 or use string methods. Always use u flag
for Unicode text!
3. Pattern Matching Methods (test, exec, match, matchAll)
Method
Syntax
Description
Returns
test()
regex.test(str)
Check if pattern matches; fastest for existence check
Boolean
exec()
regex.exec(str)
Get match details with capture groups; use in loop with /g flag
Array | null
match()
str.match(regex)
Without /g: like exec(); with /g: array of all matches (no details)
Array | null
matchAll() ES2020
str.matchAll(regex)
Iterator of all matches with full details; regex must have /g
Iterator
Example: Pattern matching methods
const str = 'The year 2025 and 2026';
// test() - simple existence check
/\d+/.test(str); // true
/cat/.test(str); // false
// Use for validation
function isEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// exec() - detailed match info
const yearRe = /(\d{4})/;
const match = yearRe.exec(str);
match[0]; // "2025" (full match)
match[1]; // "2025" (capture group 1)
match.index; // 9 (position)
match.input; // original string
// exec() with /g flag (iterate)
const yearReGlobal = /(\d{4})/g;
let m;
while ((m = yearReGlobal.exec(str)) !== null) {
console.log(m[1], 'at', m.index);
// "2025" at 9
// "2026" at 18
}
// match() without /g (same as exec)
str.match(/(\d{4})/);
// ["2025", "2025", index: 9, input: "...", groups: undefined]
// match() with /g (all matches, no details)
str.match(/\d{4}/g); // ["2025", "2026"]
str.match(/\d+/g); // ["2025", "2026"]
// matchAll() - best of both (ES2020+)
const years = str.matchAll(/(\d{4})/g);
for (const match of years) {
console.log(match[1], 'at', match.index);
// "2025" at 9
// "2026" at 18
}
// Convert to array
const allMatches = [...str.matchAll(/(\d{4})/g)];
// Named capture groups
const phoneRe = /(?<area>\d{3})-(?<exchange>\d{3})-(?<number>\d{4})/;
const phone = '555-123-4567';
const phoneMatch = phone.match(phoneRe);
phoneMatch[1]; // "555"
phoneMatch.groups.area; // "555"
phoneMatch.groups.exchange; // "123"
phoneMatch.groups.number; // "4567"
// Multiple patterns
const email = 'user@example.com';
const emailRe = /^(?<user>[^@]+)@(?<domain>[^@]+)$/;
const emailMatch = email.match(emailRe);
emailMatch.groups.user; // "user"
emailMatch.groups.domain; // "example.com"
// No match returns null
'abc'.match(/\d+/); // null
/\d+/.test('abc'); // false
/\d+/.exec('abc'); // null
// Global test() gotcha (stateful!)
const re = /test/g;
re.test('test test'); // true (first call)
re.test('test test'); // true (second call)
re.test('test test'); // false! (lastIndex past end)
re.lastIndex; // 0 (reset)
// Solution: don't reuse global regex for test()
function hasDigit(str) {
return /\d/.test(str); // no /g, always works
}
// Or reset lastIndex
function hasPattern(str, regex) {
regex.lastIndex = 0;
return regex.test(str);
}
Note: Use test() for boolean checks; match() for simple extraction;
matchAll() for all matches with details. Avoid test() with global regex in loops
(stateful).
4. String RegExp Methods (replace, replaceAll, split, search)
Method
Syntax
Description
Returns
replace()
str.replace(regex, newStr|fn)
Replace first match (or all with /g); newStr can use $1, $2, etc.
String
replaceAll() ES2021
str.replaceAll(str|regex, newStr|fn)
Replace all occurrences; regex must have /g flag
String
split()
str.split(regex, limit)
Split string by pattern; optional limit; capture groups included in result
Array
search()
str.search(regex)
Find index of first match; ignores /g flag; like indexOf for regex
Index | -1
Replacement Patterns (in replace newStr argument)
Pattern
Inserts
Example
Result
$$
Literal $
'$10'.replace(/\d+/, '$$&')
"$&10"
$&
Entire match
'cat'.replace(/\w+/, '[$&]')
"[cat]"
$`
Text before match
'a-b'.replace(/-/, '$`')
"aab"
$'
Text after match
'a-b'.replace(/-/, "$'")
"abb"
$n
Capture group n (1-99)
'John Doe'.replace(/(\w+) (\w+)/, '$2, $1')
"Doe, John"
$<name>
Named capture group
'2025-12-17'.replace(/(?<y>\d+)-(?<m>\d+)-(?<d>\d+)/, '$<m>/$<d>/$<y>')
"12/17/2025"
\')'); // "a(a|b)b"
// Capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"
// Named groups
const date = '2025-12-17';
date.replace(
/(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/,
'
const str = 'Hello World, hello again';
// replace - first match
str.replace('hello', 'hi'); // "Hello World, hi again"
str.replace(/hello/, 'hi'); // "Hello World, hi again"
// replace - all matches (with /g)
str.replace(/hello/gi, 'hi'); // "hi World, hi again"
// replaceAll - simpler for strings
'aaa'.replace(/a/g, 'b'); // "bbb"
'aaa'.replaceAll('a', 'b'); // "bbb"
// Replacement patterns
'test'.replace(/test/, '[$&]'); // "[test]"
'a-b'.replace(/-/, '($`|$\')'); // "a(a|b)b"
// Capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"
// Named groups
const date = '2025-12-17';
date.replace(
/(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/,
'$<month>/$<day>/$<year>'
); // "12/17/2025"
// Replace with function
str.replace(/\b\w+\b/g, match => match.toUpperCase());
// "HELLO WORLD, HELLO AGAIN"
// Function receives: match, p1, p2, ..., offset, string, groups
'1 plus 2 equals 3'.replace(/(\d+)/g, (match, p1, offset) => {
return parseInt(p1) * 2;
}); // "2 plus 4 equals 6"
// Advanced replacements
const text = 'Price: $10.50, Total: $20.00';
text.replace(/\$(\d+\.\d+)/g, (match, price) => {
return `$${(parseFloat(price) * 1.1).toFixed(2)}`;
}); // "Price: $11.55, Total: $22.00"
// split - by regex
'a1b2c3'.split(/\d/); // ["a", "b", "c", ""]
'one,two;three:four'.split(/[,;:]/);
// ["one", "two", "three", "four"]
// split - with limit
'a-b-c-d'.split(/-/, 2); // ["a", "b"]
// split - capture groups included in result
'a1b2c'.split(/(\d)/);
// ["a", "1", "b", "2", "c"]
// split - whitespace
' hello world '.split(/\s+/);
// ["", "hello", "world", ""]
' hello world '.trim().split(/\s+/);
// ["hello", "world"]
// search - find index
str.search(/world/i); // 6
str.search(/cat/); // -1
// search vs indexOf
'hello'.indexOf('l'); // 2 (first 'l')
'hello'.search(/l/); // 2 (first 'l')
// search with patterns
'abc123xyz'.search(/\d/); // 3 (first digit)
'test@email.com'.search(/@/); // 4
// Use cases
// 1. Format phone numbers
function formatPhone(phone) {
return phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
}
formatPhone('5551234567'); // "(555) 123-4567"
// 2. Sanitize HTML
function escapeHtml(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// 3. Slugify
function slugify(str) {
return str
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
}
slugify('Hello World!'); // "hello-world"
// 4. Extract data
const email = 'Contact: user@example.com';
const match = email.match(/[\w.]+@[\w.]+/);
const emailAddr = match ? match[0] : null;
#x3C;month>/
const str = 'Hello World, hello again';
// replace - first match
str.replace('hello', 'hi'); // "Hello World, hi again"
str.replace(/hello/, 'hi'); // "Hello World, hi again"
// replace - all matches (with /g)
str.replace(/hello/gi, 'hi'); // "hi World, hi again"
// replaceAll - simpler for strings
'aaa'.replace(/a/g, 'b'); // "bbb"
'aaa'.replaceAll('a', 'b'); // "bbb"
// Replacement patterns
'test'.replace(/test/, '[$&]'); // "[test]"
'a-b'.replace(/-/, '($`|$\')'); // "a(a|b)b"
// Capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"
// Named groups
const date = '2025-12-17';
date.replace(
/(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/,
'$<month>/$<day>/$<year>'
); // "12/17/2025"
// Replace with function
str.replace(/\b\w+\b/g, match => match.toUpperCase());
// "HELLO WORLD, HELLO AGAIN"
// Function receives: match, p1, p2, ..., offset, string, groups
'1 plus 2 equals 3'.replace(/(\d+)/g, (match, p1, offset) => {
return parseInt(p1) * 2;
}); // "2 plus 4 equals 6"
// Advanced replacements
const text = 'Price: $10.50, Total: $20.00';
text.replace(/\$(\d+\.\d+)/g, (match, price) => {
return `$${(parseFloat(price) * 1.1).toFixed(2)}`;
}); // "Price: $11.55, Total: $22.00"
// split - by regex
'a1b2c3'.split(/\d/); // ["a", "b", "c", ""]
'one,two;three:four'.split(/[,;:]/);
// ["one", "two", "three", "four"]
// split - with limit
'a-b-c-d'.split(/-/, 2); // ["a", "b"]
// split - capture groups included in result
'a1b2c'.split(/(\d)/);
// ["a", "1", "b", "2", "c"]
// split - whitespace
' hello world '.split(/\s+/);
// ["", "hello", "world", ""]
' hello world '.trim().split(/\s+/);
// ["hello", "world"]
// search - find index
str.search(/world/i); // 6
str.search(/cat/); // -1
// search vs indexOf
'hello'.indexOf('l'); // 2 (first 'l')
'hello'.search(/l/); // 2 (first 'l')
// search with patterns
'abc123xyz'.search(/\d/); // 3 (first digit)
'test@email.com'.search(/@/); // 4
// Use cases
// 1. Format phone numbers
function formatPhone(phone) {
return phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
}
formatPhone('5551234567'); // "(555) 123-4567"
// 2. Sanitize HTML
function escapeHtml(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// 3. Slugify
function slugify(str) {
return str
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
}
slugify('Hello World!'); // "hello-world"
// 4. Extract data
const email = 'Contact: user@example.com';
const match = email.match(/[\w.]+@[\w.]+/);
const emailAddr = match ? match[0] : null;
#x3C;day>/
const str = 'Hello World, hello again';
// replace - first match
str.replace('hello', 'hi'); // "Hello World, hi again"
str.replace(/hello/, 'hi'); // "Hello World, hi again"
// replace - all matches (with /g)
str.replace(/hello/gi, 'hi'); // "hi World, hi again"
// replaceAll - simpler for strings
'aaa'.replace(/a/g, 'b'); // "bbb"
'aaa'.replaceAll('a', 'b'); // "bbb"
// Replacement patterns
'test'.replace(/test/, '[$&]'); // "[test]"
'a-b'.replace(/-/, '($`|$\')'); // "a(a|b)b"
// Capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"
// Named groups
const date = '2025-12-17';
date.replace(
/(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/,
'$<month>/$<day>/$<year>'
); // "12/17/2025"
// Replace with function
str.replace(/\b\w+\b/g, match => match.toUpperCase());
// "HELLO WORLD, HELLO AGAIN"
// Function receives: match, p1, p2, ..., offset, string, groups
'1 plus 2 equals 3'.replace(/(\d+)/g, (match, p1, offset) => {
return parseInt(p1) * 2;
}); // "2 plus 4 equals 6"
// Advanced replacements
const text = 'Price: $10.50, Total: $20.00';
text.replace(/\$(\d+\.\d+)/g, (match, price) => {
return `$${(parseFloat(price) * 1.1).toFixed(2)}`;
}); // "Price: $11.55, Total: $22.00"
// split - by regex
'a1b2c3'.split(/\d/); // ["a", "b", "c", ""]
'one,two;three:four'.split(/[,;:]/);
// ["one", "two", "three", "four"]
// split - with limit
'a-b-c-d'.split(/-/, 2); // ["a", "b"]
// split - capture groups included in result
'a1b2c'.split(/(\d)/);
// ["a", "1", "b", "2", "c"]
// split - whitespace
' hello world '.split(/\s+/);
// ["", "hello", "world", ""]
' hello world '.trim().split(/\s+/);
// ["hello", "world"]
// search - find index
str.search(/world/i); // 6
str.search(/cat/); // -1
// search vs indexOf
'hello'.indexOf('l'); // 2 (first 'l')
'hello'.search(/l/); // 2 (first 'l')
// search with patterns
'abc123xyz'.search(/\d/); // 3 (first digit)
'test@email.com'.search(/@/); // 4
// Use cases
// 1. Format phone numbers
function formatPhone(phone) {
return phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
}
formatPhone('5551234567'); // "(555) 123-4567"
// 2. Sanitize HTML
function escapeHtml(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// 3. Slugify
function slugify(str) {
return str
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
}
slugify('Hello World!'); // "hello-world"
// 4. Extract data
const email = 'Contact: user@example.com';
const match = email.match(/[\w.]+@[\w.]+/);
const emailAddr = match ? match[0] : null;
#x3C;year>'
); // "12/17/2025"
// Replace with function
str.replace(/\b\w+\b/g, match => match.toUpperCase());
// "HELLO WORLD, HELLO AGAIN"
// Function receives: match, p1, p2, ..., offset, string, groups
'1 plus 2 equals 3'.replace(/(\d+)/g, (match, p1, offset) => {
return parseInt(p1) * 2;
}); // "2 plus 4 equals 6"
// Advanced replacements
const text = 'Price: $10.50, Total: $20.00';
text.replace(/\$(\d+\.\d+)/g, (match, price) => {
return `${(parseFloat(price) * 1.1).toFixed(2)}`;
}); // "Price: $11.55, Total: $22.00"
// split - by regex
'a1b2c3'.split(/\d/); // ["a", "b", "c", ""]
'one,two;three:four'.split(/[,;:]/);
// ["one", "two", "three", "four"]
// split - with limit
'a-b-c-d'.split(/-/, 2); // ["a", "b"]
// split - capture groups included in result
'a1b2c'.split(/(\d)/);
// ["a", "1", "b", "2", "c"]
// split - whitespace
' hello world '.split(/\s+/);
// ["", "hello", "world", ""]
' hello world '.trim().split(/\s+/);
// ["hello", "world"]
// search - find index
str.search(/world/i); // 6
str.search(/cat/); // -1
// search vs indexOf
'hello'.indexOf('l'); // 2 (first 'l')
'hello'.search(/l/); // 2 (first 'l')
// search with patterns
'abc123xyz'.search(/\d/); // 3 (first digit)
'test@email.com'.search(/@/); // 4
// Use cases
// 1. Format phone numbers
function formatPhone(phone) {
return phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
}
formatPhone('5551234567'); // "(555) 123-4567"
// 2. Sanitize HTML
function escapeHtml(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// 3. Slugify
function slugify(str) {
return str
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
}
slugify('Hello World!'); // "hello-world"
// 4. Extract data
const email = 'Contact: user@example.com';
const match = email.match(/[\w.]+@[\w.]+/);
const emailAddr = match ? match[0] : null;
Note: Use
replace() with functions for complex transformations.
replaceAll() requires /g flag for regex. split() with capture groups includes
delimiters in result.
5. Character Classes ([abc], \d, \w, \s) and Quantifiers (+, *, ?, {n})
| Pattern | Matches | Description | Example |
|---|---|---|---|
| . | Any character | Except newline (unless /s flag); use [\s\S] or /s for any |
/a.c/.test('abc') // true |
| \d | Digit [0-9] | ASCII digits only; use \p{N} with /u for Unicode digits |
/\d+/.test('123') // true |
| \D | Non-digit [^0-9] | Anything except ASCII digits | /\D/.test('a') // true |
| \w | Word [A-Za-z0-9_] | Alphanumeric + underscore; ASCII only | /\w+/.test('hello') // true |
| \W | Non-word [^\w] | Anything except word characters | /\W/.test('@') // true |
| \s | Whitespace | Space, tab, newline, etc.: [ \t\n\r\f\v] | /\s+/.test('a b') // true |
| \S | Non-whitespace | Anything except whitespace | /\S/.test('a') // true |
| [abc] | Character set | Any single character in set; can use ranges | /[aeiou]/.test('e') // true |
| [^abc] | Negated set | Any character NOT in set | /[^0-9]/.test('a') // true |
| [a-z] | Range | Character range; case-sensitive | /[a-z]/.test('m') // true |
| \b | Word boundary | Position between \w and \W; zero-width | /\bword\b/.test('a word') // true |
| \B | Non-word boundary | Opposite of \b | /\Bword/.test('sword') // true |
| ^ | Start of string/line | String start, or line start with /m flag | /^hello/.test('hello') // true |
| $ | End of string/line | String end, or line end with /m flag | /world$/.test('world') // true |
| Quantifier | Matches | Description | Example |
|---|---|---|---|
| * | 0 or more | Greedy; matches as many as possible | /a*/.test('') // true |
| + | 1 or more | Greedy; at least one required | /a+/.test('aaa') // true |
| ? | 0 or 1 | Optional; makes preceding element optional | /colou?r/.test('color') // true |
| {n} | Exactly n | Precise count | /\d{3}/.test('123') // true |
| {n,} | n or more | At least n | /\d{2,}/.test('12') // true |
| {n,m} | Between n and m | Inclusive range | /\d{2,4}/.test('123') // true |
| *? | 0+ (lazy) | Non-greedy; matches as few as possible | /<.*?>/.exec('<a><b>')[0] // "<a>" |
| +? | 1+ (lazy) | Non-greedy; at least one | /a+?/.exec('aaa')[0] // "a" |
| ?? | 0-1 (lazy) | Non-greedy optional | /a??/.exec('aa')[0] // "" |
| {n,m}? | n-m (lazy) | Non-greedy range | /\d{2,4}?/.exec('12345')[0] // "12" |
Example: Character classes and quantifiers
// Basic character classes
/\d+/.test('123'); // true (one or more digits)
/\D+/.test('abc'); // true (one or more non-digits)
/\w+/.test('hello_123'); // true (word characters)
/\s+/.test(' '); // true (whitespace)
// Dot (any character except newline)
/.+/.test('hello'); // true
/.+/.test('hello\nworld'); // false (. doesn't match \n)
/.+/s.test('hello\nworld'); // true (dotAll flag)
// Character sets
/[aeiou]/.test('hello'); // true (has vowel)
/[0-9]/.test('5'); // true (digit)
/[a-z]/i.test('A'); // true (case insensitive)
/[a-zA-Z0-9]/.test('x'); // true (alphanumeric)
// Negated sets
/[^0-9]/.test('a'); // true (not a digit)
/[^aeiou]/.test('b'); // true (not a vowel)
// Ranges
/[a-z]/.test('m'); // true
/[A-Z]/.test('M'); // true
/[0-9]/.test('5'); // true
/[a-zA-Z]/.test('X'); // true
// Word boundaries
/\bhello\b/.test('hello world'); // true
/\bhello\b/.test('helloworld'); // false
/\bworld\b/.test('hello world'); // true
// Extract whole words
'hello world'.match(/\b\w+\b/g); // ["hello", "world"]
// Start/End anchors
/^hello/.test('hello world'); // true
/^hello/.test('say hello'); // false
/world$/.test('hello world'); // true
/world$/.test('world peace'); // false
// Full string match
/^hello$/.test('hello'); // true
/^hello$/.test('hello world'); // false
// Greedy quantifiers (default)
'<a><b><c>'.match(/<.+>/)[0]; // "<a><b><c>" (greedy)
'aaaa'.match(/a+/)[0]; // "aaaa" (all)
// Non-greedy (lazy) quantifiers
'<a><b><c>'.match(/<.+?>/g); // ["<a>", "<b>", "<c>"]
'aaaa'.match(/a+?/)[0]; // "a" (minimal)
// Practical examples
// 1. Email (simple)
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test('user@example.com'); // true
// 2. Phone number
/^\d{3}-\d{3}-\d{4}$/.test('555-123-4567'); // true
// 3. Hex color
/^#[0-9A-F]{6}$/i.test('#FF5733'); // true
// 4. URL (simple)
/^https?:\/\/[\w.-]+/.test('https://example.com'); // true
// 5. Username (alphanumeric, 3-16 chars)
/^\w{3,16}$/.test('user_123'); // true
// 6. Password (at least 8 chars, has digit)
/^(?=.*\d).{8,}$/.test('Pass123!'); // true
// 7. Extract hashtags
'#javascript #coding #webdev'.match(/#\w+/g);
// ["#javascript", "#coding", "#webdev"]
// 8. Match HTML tags (non-greedy)
'<div>Hello</div>'.match(/<[^>]+>/g);
// ["<div>", "</div>"]
// 9. Trim whitespace
' hello '.replace(/^\s+|\s+$/g, ''); // "hello"
// 10. Multiple spaces to single
'hello world'.replace(/\s+/g, ' '); // "hello world"
// Combining patterns
const urlRe = /^(https?:\/\/)?([\w.-]+)(:\d+)?(\/[\w.-]*)*\??/;
// Optional elements with ?
/colou?r/.test('color'); // true (US)
/colou?r/.test('colour'); // true (UK)
// Exact count
/^\d{5}$/.test('12345'); // true (5 digits)
/^\d{3}-\d{2}-\d{4}$/.test('123-45-6789'); // true (SSN format)
// Range
/^[a-z]{3,10}$/i.test('hello'); // true (3-10 letters)
Warning: Greedy quantifiers (
+, *) can cause catastrophic
backtracking with complex patterns. Use lazy quantifiers (+?, *?) or atomic groups
when appropriate.
6. Lookahead (?=) and Lookbehind (?<=) Assertions
| Assertion | Syntax | Description | Example |
|---|---|---|---|
| Positive Lookahead | (?=pattern) |
Assert pattern matches ahead; zero-width (doesn't consume) | /\d(?=px)/.exec('5px')[0] // "5" |
| Negative Lookahead | (?!pattern) |
Assert pattern does NOT match ahead; zero-width | /\d(?!px)/.test('5em') // true |
| Positive Lookbehind ES2018 | (?<=pattern) |
Assert pattern matches behind; zero-width | /(?<=\$)\d+/.exec('$50')[0] // "50" |
| Negative Lookbehind ES2018 | (?<!pattern) |
Assert pattern does NOT match behind; zero-width | /(?<!\$)\d+/.test('€50') // true |
Example: Lookahead and lookbehind
// Positive lookahead (?=...)
// Match digit followed by "px" but don't include "px"
'5px 10em'.match(/\d+(?=px)/g); // ["5"]
// Password validation (at least one digit)
/^(?=.*\d).{8,}$/.test('Pass123!'); // true
/^(?=.*\d).{8,}$/.test('Password'); // false (no digit)
// Multiple lookaheads (AND conditions)
const strongPassword = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&]).{8,}$/;
strongPassword.test('Pass123!'); // true
strongPassword.test('password'); // false (no uppercase, digit, special)
// Negative lookahead (?!...)
// Match word NOT followed by "script"
'Java JavaScript'.match(/\bJava(?!Script)\b/); // ["Java"]
// Select numbers not followed by px
'5px 10em 15%'.match(/\d+(?!px)/g); // ["1", "0", "1", "5"]
// (matches last digit of 10, and 15)
// Better: not followed by px unit
'5px 10em 15%'.match(/\d+(?!px\b)/g); // ["10", "15"]
// Positive lookbehind (?<=...)
// Match digits preceded by $
'$50 €30 £40'.match(/(?<=\$)\d+/g); // ["50"]
// Extract value after key
'price=100 quantity=5'.match(/(?<==)\d+/g);
// ["100", "5"]
// Match word after "Mr. "
'Mr. Smith, Ms. Jones'.match(/(?<=Mr\. )\w+/);
// ["Smith"]
// Negative lookbehind (?<!...)
// Match digits NOT preceded by $
'$50 €30 £40'.match(/(?<!\$)\d+/g); // ["30", "40"]
// Word not preceded by "no "
'no cats, dogs, no birds'.match(/(?<!no )\b\w+s\b/g);
// ["dogs"] (cats and birds are preceded by "no ")
// Complex combinations
// 1. Price in $ (with decimal)
const priceRe = /(?<=\$)\d+(?:\.\d{2})?(?=\s|$)/g;
'Items: $10.50 and $25.00'.match(priceRe);
// ["10.50", "25.00"]
// 2. Password: 8+ chars, has lowercase, uppercase, digit
const passRe = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
passRe.test('Secure123'); // true
passRe.test('secure123'); // false (no uppercase)
passRe.test('SECURE123'); // false (no lowercase)
passRe.test('SecurePass'); // false (no digit)
// 3. Extract hashtags not in code blocks
// (not preceded by backtick)
const text = 'Use #javascript but not `#code`';
text.match(/(?<!`[^`]*)#\w+(?![^`]*`)/g); // ["#javascript"]
// 4. Numbers with thousands separator
const numRe = /\b(?<!\d,)\d{1,3}(?:,\d{3})*(?!\d)/g;
'1,000 and 10,000 and 999'.match(numRe);
// ["1,000", "10,000", "999"]
// 5. Email username (before @)
const emailRe = /\w+(?=@)/;
'user@example.com'.match(emailRe)[0]; // "user"
// 6. URL domain (after //)
const domainRe = /(?<=:\/\/)[^\/]+/;
'https://example.com/path'.match(domainRe)[0];
// "example.com"
// 7. Word boundaries with punctuation
// Word not followed by alphanumeric
'hello, world!'.match(/\w+(?!\w)/g);
// ["hello", "world"]
// 8. CSS selector (class not in comments)
const css = '.btn { } /* .comment */';
css.match(/(?<!\/\*.*)\.[a-z-]+(?!.*\*\/)/g);
// Tricky - better parsed differently
// Zero-width assertion chaining
// Number between 1-100
/^(?=\d{1,3}$)(?!0$)([1-9]\d?|100)$/.test('50'); // true
/^(?=\d{1,3}$)(?!0$)([1-9]\d?|100)$/.test('0'); // false
/^(?=\d{1,3}$)(?!0$)([1-9]\d?|100)$/.test('101'); // false
// Assertions don't consume characters
const re = /(?=\d{3})\d{2}/;
re.exec('12345'); // ["12"] (matched at position 0)
// Lookahead checked 3 digits, but only consumed 2
// Use in replace
// Insert comma in numbers
'1234567'.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
// "1,234,567"
Note: Assertions are zero-width (don't consume characters).
Lookaheads work everywhere; lookbehinds require ES2018+. Use for complex validations and extractions.
7. Named Capture Groups (?) and Replacement
| Feature | Syntax | Description | Example |
|---|---|---|---|
| Named Group | (?<name>pattern) |
Capture group with identifier; access via .groups property | /(?<year>\d{4})/ |
| Group Reference | \k<name> |
Backreference to named group within pattern | /(?<tag>\w+)</\k<tag>/ |
| Replacement | $<name> |
Reference named group in replacement string | .replace(re, '$<name>') |
| Non-capturing Group | (?:pattern) |
Group without capturing; for grouping only; better performance | /(?:abc)+/ |
| Numbered Group | (pattern) |
Traditional capture group; access via index | /(\d+)/ |
| Backreference | \1 \2 ... |
Reference numbered group within pattern | /(\w)\1/ (double char) |
Example: Named groups and backreferences
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);
match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"
// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"
// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);
match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"
// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"
// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"
// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
return `${groups.month}/${groups.day}/${groups.year}`;
});
// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)
// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false
// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"
// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"
// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"
// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)
// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)
// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)
// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"
// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true
// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"
// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]
// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)
// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
/(?<first>\w+) (?<last>\w+)/g,
(match, first, last, offset, string, groups) => {
return `${groups.last}, ${groups.first}`;
}
);
// "Smith, John, Doe, Jane"
// Case conversion in replacement
'user_name'.replace(
/_(?<char>\w)/g,
(match, char, offset, string, groups) => {
return groups.char.toUpperCase();
}
);
// "userName"
// Swap parts
'firstName lastName'.replace(
/(?<first>\w+) (?<last>\w+)/,
'$<last> $<first>'
);
// "lastName firstName"
// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}
'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}
// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);
for (const match of matches) {
console.log(match.groups.role, match.groups.id);
// "user" "123"
// "admin" "456"
}
#x3C;month>/
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);
match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"
// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"
// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"
// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
return `${groups.month}/${groups.day}/${groups.year}`;
});
// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)
// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false
// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"
// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"
// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"
// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)
// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)
// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)
// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"
// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true
// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"
// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]
// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)
// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
/(?<first>\w+) (?<last>\w+)/g,
(match, first, last, offset, string, groups) => {
return `${groups.last}, ${groups.first}`;
}
);
// "Smith, John, Doe, Jane"
// Case conversion in replacement
'user_name'.replace(
/_(?<char>\w)/g,
(match, char, offset, string, groups) => {
return groups.char.toUpperCase();
}
);
// "userName"
// Swap parts
'firstName lastName'.replace(
/(?<first>\w+) (?<last>\w+)/,
'$<last> $<first>'
);
// "lastName firstName"
// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}
'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}
// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);
for (const match of matches) {
console.log(match.groups.role, match.groups.id);
// "user" "123"
// "admin" "456"
}
#x3C;day>/
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);
match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"
// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"
// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"
// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
return `${groups.month}/${groups.day}/${groups.year}`;
});
// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)
// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false
// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"
// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"
// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"
// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)
// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)
// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)
// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"
// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true
// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"
// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]
// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)
// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
/(?<first>\w+) (?<last>\w+)/g,
(match, first, last, offset, string, groups) => {
return `${groups.last}, ${groups.first}`;
}
);
// "Smith, John, Doe, Jane"
// Case conversion in replacement
'user_name'.replace(
/_(?<char>\w)/g,
(match, char, offset, string, groups) => {
return groups.char.toUpperCase();
}
);
// "userName"
// Swap parts
'firstName lastName'.replace(
/(?<first>\w+) (?<last>\w+)/,
'$<last> $<first>'
);
// "lastName firstName"
// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}
'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}
// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);
for (const match of matches) {
console.log(match.groups.role, match.groups.id);
// "user" "123"
// "admin" "456"
}
#x3C;year>');
// "12/17/2025"
// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
return `${groups.month}/${groups.day}/${groups.year}`;
});
// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)
// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false
// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"
// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"
// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '(
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);
match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"
// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"
// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"
// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
return `${groups.month}/${groups.day}/${groups.year}`;
});
// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)
// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false
// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"
// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"
// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"
// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)
// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)
// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)
// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"
// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true
// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"
// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]
// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)
// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
/(?<first>\w+) (?<last>\w+)/g,
(match, first, last, offset, string, groups) => {
return `${groups.last}, ${groups.first}`;
}
);
// "Smith, John, Doe, Jane"
// Case conversion in replacement
'user_name'.replace(
/_(?<char>\w)/g,
(match, char, offset, string, groups) => {
return groups.char.toUpperCase();
}
);
// "userName"
// Swap parts
'firstName lastName'.replace(
/(?<first>\w+) (?<last>\w+)/,
'$<last> $<first>'
);
// "lastName firstName"
// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}
'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}
// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);
for (const match of matches) {
console.log(match.groups.role, match.groups.id);
// "user" "123"
// "admin" "456"
}
#x3C;area>)
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);
match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"
// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"
// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"
// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
return `${groups.month}/${groups.day}/${groups.year}`;
});
// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)
// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false
// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"
// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"
// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"
// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)
// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)
// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)
// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"
// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true
// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"
// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]
// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)
// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
/(?<first>\w+) (?<last>\w+)/g,
(match, first, last, offset, string, groups) => {
return `${groups.last}, ${groups.first}`;
}
);
// "Smith, John, Doe, Jane"
// Case conversion in replacement
'user_name'.replace(
/_(?<char>\w)/g,
(match, char, offset, string, groups) => {
return groups.char.toUpperCase();
}
);
// "userName"
// Swap parts
'firstName lastName'.replace(
/(?<first>\w+) (?<last>\w+)/,
'$<last> $<first>'
);
// "lastName firstName"
// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}
'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}
// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);
for (const match of matches) {
console.log(match.groups.role, match.groups.id);
// "user" "123"
// "admin" "456"
}
#x3C;exchange>-
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);
match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"
// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"
// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"
// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
return `${groups.month}/${groups.day}/${groups.year}`;
});
// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)
// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false
// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"
// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"
// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"
// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)
// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)
// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)
// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"
// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true
// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"
// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]
// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)
// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
/(?<first>\w+) (?<last>\w+)/g,
(match, first, last, offset, string, groups) => {
return `${groups.last}, ${groups.first}`;
}
);
// "Smith, John, Doe, Jane"
// Case conversion in replacement
'user_name'.replace(
/_(?<char>\w)/g,
(match, char, offset, string, groups) => {
return groups.char.toUpperCase();
}
);
// "userName"
// Swap parts
'firstName lastName'.replace(
/(?<first>\w+) (?<last>\w+)/,
'$<last> $<first>'
);
// "lastName firstName"
// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}
'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}
// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);
for (const match of matches) {
console.log(match.groups.role, match.groups.id);
// "user" "123"
// "admin" "456"
}
#x3C;number>');
// "(555) 123-4567"
// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)
// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)
// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)
// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"
// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true
// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"
// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]
// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)
// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
/(?<first>\w+) (?<last>\w+)/g,
(match, first, last, offset, string, groups) => {
return `${groups.last}, ${groups.first}`;
}
);
// "Smith, John, Doe, Jane"
// Case conversion in replacement
'user_name'.replace(
/_(?<char>\w)/g,
(match, char, offset, string, groups) => {
return groups.char.toUpperCase();
}
);
// "userName"
// Swap parts
'firstName lastName'.replace(
/(?<first>\w+) (?<last>\w+)/,
'
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);
match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"
// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"
// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"
// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
return `${groups.month}/${groups.day}/${groups.year}`;
});
// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)
// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false
// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"
// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"
// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"
// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)
// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)
// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)
// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"
// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true
// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"
// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]
// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)
// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
/(?<first>\w+) (?<last>\w+)/g,
(match, first, last, offset, string, groups) => {
return `${groups.last}, ${groups.first}`;
}
);
// "Smith, John, Doe, Jane"
// Case conversion in replacement
'user_name'.replace(
/_(?<char>\w)/g,
(match, char, offset, string, groups) => {
return groups.char.toUpperCase();
}
);
// "userName"
// Swap parts
'firstName lastName'.replace(
/(?<first>\w+) (?<last>\w+)/,
'$<last> $<first>'
);
// "lastName firstName"
// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}
'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}
// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);
for (const match of matches) {
console.log(match.groups.role, match.groups.id);
// "user" "123"
// "admin" "456"
}
#x3C;last>
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);
match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"
// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"
// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"
// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
return `${groups.month}/${groups.day}/${groups.year}`;
});
// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)
// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false
// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"
// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"
// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"
// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)
// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)
// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)
// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"
// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true
// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"
// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]
// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)
// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
/(?<first>\w+) (?<last>\w+)/g,
(match, first, last, offset, string, groups) => {
return `${groups.last}, ${groups.first}`;
}
);
// "Smith, John, Doe, Jane"
// Case conversion in replacement
'user_name'.replace(
/_(?<char>\w)/g,
(match, char, offset, string, groups) => {
return groups.char.toUpperCase();
}
);
// "userName"
// Swap parts
'firstName lastName'.replace(
/(?<first>\w+) (?<last>\w+)/,
'$<last> $<first>'
);
// "lastName firstName"
// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}
'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}
// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);
for (const match of matches) {
console.log(match.groups.role, match.groups.id);
// "user" "123"
// "admin" "456"
}
#x3C;first>'
);
// "lastName firstName"
// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}
'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}
// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);
for (const match of matches) {
console.log(match.groups.role, match.groups.id);
// "user" "123"
// "admin" "456"
}
Note: Named groups improve readability and maintainability. Use
(?:...) for
grouping without capturing (better performance). Backreferences \k<name> or \1
match same text as group.
Section 11 Summary
- Creation: Literal
/pattern/flagsfor static (compile-time); constructornew RegExp(str, flags)for dynamic (escape backslashes:'\\d') - Flags:
g(global),i(case-insensitive),m(multiline ^$),s(dotAll),u(Unicode),y(sticky),d(indices); g makes regex stateful - Methods:
test()boolean;exec()details + loop with g;match()simple/all;matchAll()iterator with details (requires g) - String methods:
replace/replaceAllmodify;splitdivide (includes captures);searchfind index; use$1 $<name>in replacement - Patterns:
\d \w \sclasses;. [abc] [^abc] [a-z]sets;^ $ \banchors;+ * ? {n,m}quantifiers; add?for lazy - Assertions:
(?=...) (?!...)lookahead;(?<=...) (?<!...)lookbehind; zero-width (don't consume); chain for complex validation - Groups:
(?<name>...)named capture;(?:...)non-capturing;\k<name> \1backreferences;$<name> $1in replacements