Methods of RegExp and String in JavaScript
JavaScript provides regex functionality through two sides: methods on String objects that accept regular expressions, and methods on RegExp objects that accept strings. Knowing which method to use in each situation is the difference between writing clean, efficient pattern-matching code and struggling with unexpected results.
This guide covers every method available for working with regular expressions in JavaScript. You will learn the exact behavior of each method, how flags change their output, what they return on success and failure, and which method to reach for in every common scenario.
str.match(regexp): With and Without the g Flag
The match() method is probably the most frequently used regex method on strings. Its behavior changes dramatically depending on whether the regex has the g (global) flag.
Without the g Flag: Detailed Single Match
When the regex does not have the g flag, match() returns a result array containing the first match along with capturing group information, or null if there is no match.
const str = 'Date: 2024-03-15, Time: 14:30';
const result = str.match(/(\d{4})-(\d{2})-(\d{2})/);
console.log(result);
Output:
[
'2024-03-15', // result[0] (the full match)
'2024', // result[1] (first capturing group)
'03', // result[2] (second capturing group)
'15', // result[3] (third capturing group)
index: 6, // position of the match in the string
input: 'Date: 2024-03-15, Time: 14:30', // the original string
groups: undefined // named groups (none in this example)
]
The returned array has special properties:
| Property | Description |
|---|---|
[0] | The full matched substring |
[1], [2], ... | Captured groups |
index | Starting position of the match |
input | The original string |
groups | Object of named capturing groups (or undefined) |
Named Capturing Groups
When you use named groups, the groups property is populated:
const str = 'John is 30 years old';
const result = str.match(/(?<name>\w+) is (?<age>\d+)/);
console.log(result.groups.name); // "John"
console.log(result.groups.age); // "30"
console.log(result[1]); // "John" (also accessible by index)
console.log(result[2]); // "30"
With the g Flag: Array of All Matches
When the regex has the g flag, match() returns a plain array of all matched substrings, without any group information, index, or extra properties.
const str = 'cats and dogs and cats and birds';
const result = str.match(/cats/g);
console.log(result); // ["cats", "cats"]
A more practical example:
const html = '<div class="box"> <span id="title"> <a href="#">';
const tags = html.match(/<\w+/g);
console.log(tags); // ["<div", "<span", "<a"]
With the g flag, match() returns only the matched strings. You lose all capturing group data, indexes, and the groups property. If you need that information for all matches, use matchAll() instead.
When There Is No Match
Both variants return null when no match is found:
const str = 'hello world';
console.log(str.match(/xyz/)); // null
console.log(str.match(/xyz/g)); // null
Always guard against null before working with the result:
// Common mistake: crashes if no match
const count = str.match(/xyz/g).length; // TypeError: Cannot read properties of null
// Safe approach
const matches = str.match(/xyz/g);
const count2 = matches ? matches.length : 0;
// Or with nullish coalescing
const count3 = (str.match(/xyz/g) ?? []).length;
Passing a String Instead of RegExp
If you pass a plain string to match(), JavaScript automatically converts it to a RegExp:
const str = 'Price: $10.99';
// These are equivalent:
console.log(str.match('\\d+')); // ["10"]
console.log(str.match(/\d+/)); // ["10"]
When passing a string, you must double-escape backslashes (\\d instead of \d). Using a regex literal is almost always clearer.
str.matchAll(regexp): Iterator of All Matches
The matchAll() method was introduced in ES2020 to solve the main limitation of match() with the g flag: losing group information. It returns an iterator that yields detailed match objects for every match.
Basic Usage
const str = 'test1 test2 test3';
const regex = /test(\d)/g;
const iterator = str.matchAll(regex);
for (const match of iterator) {
console.log(`Full: "${match[0]}", Group: "${match[1]}", Index: ${match.index}`);
}
Output:
Full: "test1", Group: "1", Index: 0
Full: "test2", Group: "2", Index: 6
Full: "test3", Group: "3", Index: 12
Each iteration yields a match object identical to what match() without the g flag would return: full match, captured groups, index, input, and groups.
The g Flag Is Required
matchAll() throws a TypeError if the regex does not have the g flag:
const str = 'hello world';
// This throws!
try {
str.matchAll(/hello/);
} catch (e) {
console.log(e.message);
// "String.prototype.matchAll called with a non-global RegExp argument"
}
// Correct usage
const matches = str.matchAll(/hello/g);
Converting to an Array
Since matchAll() returns an iterator, you can convert it to an array with the spread operator or Array.from():
const str = 'rgb(255, 128, 0) and rgb(0, 200, 100)';
const matches = [...str.matchAll(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/g)];
console.log(matches.length); // 2
console.log(matches[0][0]); // "rgb(255, 128, 0)"
console.log(matches[0][1]); // "255"
console.log(matches[0][2]); // "128"
console.log(matches[0][3]); // "0"
console.log(matches[1][0]); // "rgb(0, 200, 100)"
console.log(matches[1][1]); // "0"
With Named Groups
matchAll() fully supports named capturing groups:
const str = '2024-03-15 and 2023-12-25';
const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/g;
for (const match of str.matchAll(regex)) {
const { year, month, day } = match.groups;
console.log(`Year: ${year}, Month: ${month}, Day: ${day}`);
}
Output:
Year: 2024, Month: 03, Day: 15
Year: 2023, Month: 12, Day: 25
matchAll() Does Not Mutate lastIndex
Unlike exec(), matchAll() does not modify the regex's lastIndex property. It creates an internal copy:
const regex = /\d+/g;
regex.lastIndex = 5;
const str = '12 34 56';
const matches = [...str.matchAll(regex)];
console.log(matches.map(m => m[0])); // ["12", "34", "56"] (starts from beginning)
console.log(regex.lastIndex); // 5 (unchanged)
Prefer matchAll() over manual exec() loops whenever you need all matches with full details. It is safer (no infinite loop risk), cleaner, and does not mutate the regex state.
str.search(regexp): First Match Index
The search() method returns the index of the first match, or -1 if no match is found. It is the regex equivalent of indexOf().
Basic Usage
const str = 'Hello, World! 123';
console.log(str.search(/\d+/)); // 14 (first digit found at index 14)
console.log(str.search(/World/)); // 7
console.log(str.search(/xyz/)); // -1 (not found)
Key Characteristics
search() has several important behaviors:
const str = 'one TWO three FOUR';
// Case-insensitive search
console.log(str.search(/two/)); // -1 (case-sensitive by default)
console.log(str.search(/two/i)); // 4 (case-insensitive)
// Always returns the FIRST match index
console.log(str.search(/[A-Z]+/g)); // 4 (the g flag is ignored)
// Ignores lastIndex
const regex = /\d/g;
regex.lastIndex = 10;
const str2 = 'abc 1 def 2';
console.log(str2.search(regex)); // 4 (ignores lastIndex, starts from 0)
console.log(regex.lastIndex); // 10 (not modified)
search() always searches from the beginning of the string. It ignores both the g flag and lastIndex. Use it only when you need the position of the first match and nothing else.
search() vs. indexOf()
const str = 'Price: $42.99';
// indexOf works with plain strings only
console.log(str.indexOf('42')); // 8
// search works with regex patterns
console.log(str.search(/\d+\.\d+/)); // 8 (matches "42.99")
// indexOf cannot do pattern matching
console.log(str.indexOf(/\d+/)); // -1 (treated as string "/\\d+/")
Use indexOf() for simple string lookups and search() when you need pattern matching.
str.replace(regexp, replacement): Replace Matches
The replace() method searches for a match and replaces it with a specified replacement. Without the g flag, it replaces only the first match. With g, it replaces all matches.
Basic Replacement
const str = 'I like cats. cats are great.';
// Without g: replaces first occurrence only
console.log(str.replace(/cats/, 'dogs'));
// "I like dogs. cats are great."
// With g: replaces all occurrences
console.log(str.replace(/cats/g, 'dogs'));
// "I like dogs. dogs are great."
Special Replacement Patterns
The replacement string supports special character sequences:
| Pattern | Inserts |
|---|---|
$$ | Literal $ character |
$& | The entire matched substring |
$` | The portion of the string before the match |
$' | The portion of the string after the match |
$1, $2, ... | Captured group by number |
$<name> | Captured group by name |
// $&: the full match
const str = 'hello world';
console.log(str.replace(/\b\w/g, '$&!!!'));
// "h!!!ello w!!!orld"
// But more usefully, to wrap matches:
console.log(str.replace(/\w+/g, '[$&]'));
// "[hello] [world]"
Using Captured Groups in Replacement
// Swap first and last name
const name = 'John Smith';
console.log(name.replace(/(\w+) (\w+)/, '$2, $1'));
// "Smith, John"
// Reformat a date
const date = '03/15/2024';
console.log(date.replace(/(\d{2})\/(\d{2})\/(\d{4})/, '$3-$1-$2'));
// "2024-03-15"
// With named groups
const date2 = '03/15/2024';
console.log(date2.replace(
/(?<month>\d{2})\/(?<day>\d{2})\/(?<year>\d{4})/,
'$<year>-$<month>-$<day>'
));
// "2024-03-15"
Replacement Function
Instead of a string, you can pass a function as the second argument. This function is called for each match, and its return value becomes the replacement:
const str = 'the quick brown fox';
const result = str.replace(/\b\w+/g, (match) => {
return match.charAt(0).toUpperCase() + match.slice(1);
});
console.log(result); // "The Quick Brown Fox"
The replacement function receives these arguments:
| Argument | Value |
|---|---|
match | The full matched substring |
p1, p2, ... | Captured groups (if any) |
offset | Index of the match in the string |
string | The original string |
groups | Named groups object (if any) |
const str = 'border-top-width';
const camelCase = str.replace(/-(\w)/g, (match, letter, offset) => {
console.log(`Match: "${match}", Letter: "${letter}", Offset: ${offset}`);
return letter.toUpperCase();
});
console.log(camelCase);
Output:
Match: "-t", Letter: "t", Offset: 6
Match: "-w", Letter: "w", Offset: 10
borderTopWidth
Practical Example: Template Engine
function renderTemplate(template, data) {
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
if (key in data) {
return data[key];
}
return match; // Leave unresolved placeholders as-is
});
}
const template = 'Hello, {{name}}! You have {{count}} messages from {{sender}}.';
const data = { name: 'Alice', count: 5 };
console.log(renderTemplate(template, data));
// "Hello, Alice! You have 5 messages from {{sender}}."
Practical Example: Escaping HTML
function escapeHTML(str) {
return str.replace(/[&<>"']/g, (char) => {
const entities = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return entities[char];
});
}
console.log(escapeHTML('<script>alert("xss")</script>'));
// "<script>alert("xss")</script>"
str.replaceAll(regexp, replacement)
Introduced in ES2021, replaceAll() replaces all occurrences without needing the g flag on string patterns. When used with a regex, the g flag is required.
With Strings
const str = 'aabbcc aabbcc aabbcc';
// replace without g: only first
console.log(str.replace('aa', 'XX'));
// "XXbbcc aabbcc aabbcc"
// replaceAll: all occurrences
console.log(str.replaceAll('aa', 'XX'));
// "XXbbcc XXbbcc XXbbcc"
With Regular Expressions
When using a regex, replaceAll() requires the g flag:
const str = 'foo123bar456baz';
// This works: g flag is present
console.log(str.replaceAll(/\d+/g, '#'));
// "foo#bar#baz"
// This throws a TypeError
try {
str.replaceAll(/\d+/, '#');
} catch (e) {
console.log(e.message);
// "String.prototype.replaceAll called with a non-global RegExp argument"
}
replaceAll() vs. replace() with g
When using regular expressions, replaceAll() with g produces the same result as replace() with g:
const str = 'one-two-three';
// These produce identical results with regex
console.log(str.replace(/-/g, '_')); // "one_two_three"
console.log(str.replaceAll(/-/g, '_')); // "one_two_three"
The real advantage of replaceAll() is with string arguments, where it replaces all occurrences without needing regex:
const str = 'a.b.c.d';
// Awkward: escaping dots in regex
console.log(str.replace(/\./g, '-')); // "a-b-c-d"
// Clean: replaceAll with a simple string
console.log(str.replaceAll('.', '-')); // "a-b-c-d"
Use replaceAll() with plain strings when you want to replace all occurrences of a literal substring. Use replace() with /pattern/g when you need regex pattern matching.
str.split(regexp)
The split() method divides a string into an array of substrings using a separator. When the separator is a regex, you get powerful splitting capabilities.
Basic Usage
// Split by one or more spaces
const str = 'hello world foo';
console.log(str.split(' '));
// ["hello", "", "", "world", "", "", "foo"] (empty strings for extra spaces)
console.log(str.split(/\s+/));
// ["hello", "world", "foo"] (regex handles multiple spaces)
Splitting with Complex Patterns
// Split by multiple delimiters
const str = 'one,two;three:four|five';
console.log(str.split(/[,;:|]/));
// ["one", "two", "three", "four", "five"]
// Split by words, keeping separators... not quite
const sentence = 'Hello World Foo Bar';
console.log(sentence.split(/\s+/));
// ["Hello", "World", "Foo", "Bar"]
Capturing Groups in split()
If the regex contains capturing groups, the captured text is included in the resulting array:
const str = 'one1two2three3four';
// Without capturing group (separators are removed)
console.log(str.split(/\d/));
// ["one", "two", "three", "four"]
// With capturing group (separators are included)
console.log(str.split(/(\d)/));
// ["one", "1", "two", "2", "three", "3", "four"]
This is useful when you need to split a string but also keep track of the delimiters:
const expression = '10+20-30*40';
const parts = expression.split(/([+\-*\/])/);
console.log(parts);
// ["10", "+", "20", "-", "30", "*", "40"]
Limiting the Number of Splits
The optional second argument limits the number of elements returned:
const str = 'a,b,c,d,e';
console.log(str.split(',', 3));
// ["a", "b", "c"]
console.log(str.split(/,/, 2));
// ["a", "b"]
Edge Cases
// Empty string
console.log(''.split(/,/));
// [""] (array with one empty string)
// No matches: returns original string in array
console.log('hello'.split(/,/));
// ["hello"]
// Regex matches at start/end: empty strings in result
console.log(',hello,world,'.split(/,/));
// ["", "hello", "world", ""]
// Splitting every character
console.log('hello'.split(/(?:)/)); // Non-capturing empty match
// ["h", "e", "l", "l", "o"]
Be careful when splitting with a regex that can match an empty string (like /\s*/). This can produce unexpected results with many empty strings in the output.
console.log('abc'.split(/\s*/));
// ["a", "b", "c"] (in most engines, but behavior can vary)
regexp.exec(str): Full Control Over Matching
While the str.match() and str.matchAll() methods are convenient, the exec() method on RegExp objects gives you the most fine-grained control over the matching process. It returns a single match at a time and updates lastIndex, allowing you to step through matches manually.
Without the g Flag
Without g, exec() behaves like match() without g. It always returns the first match, regardless of lastIndex:
const regex = /\d+/;
const str = 'abc 123 def 456';
console.log(regex.exec(str));
// ["123", index: 4, input: "abc 123 def 456", groups: undefined]
// Calling again returns the same result (no progression)
console.log(regex.exec(str));
// ["123", index: 4, ...] (same match)
With the g Flag: Stateful Iteration
With g, each call to exec() starts searching from lastIndex and updates it after a match:
const regex = /\d+/g;
const str = 'abc 12 def 345 ghi 6';
let match;
while ((match = regex.exec(str)) !== null) {
console.log(`Found "${match[0]}" at index ${match.index}, lastIndex now: ${regex.lastIndex}`);
}
Output:
Found "12" at index 4, lastIndex now: 6
Found "345" at index 11, lastIndex now: 14
Found "6" at index 19, lastIndex now: 20
When no more matches are found, exec() returns null and resets lastIndex to 0.
Capturing Groups with exec()
Unlike match() with g, exec() always provides full group information:
const regex = /(\d{4})-(\d{2})-(\d{2})/g;
const str = 'Dates: 2024-03-15 and 2023-12-25';
let match;
while ((match = regex.exec(str)) !== null) {
console.log(`Full: ${match[0]}`);
console.log(`Year: ${match[1]}, Month: ${match[2]}, Day: ${match[3]}`);
console.log(`Index: ${match.index}`);
console.log('---');
}
Output:
Full: 2024-03-15
Year: 2024, Month: 03, Day: 15
Index: 7
---
Full: 2023-12-25
Year: 2023, Month: 12, Day: 25
Index: 22
---
The Infinite Loop Trap
A common and dangerous mistake is using exec() in a loop with a regex that was meant to have the g flag but does not, or creating the regex inside the loop:
// DANGER: Infinite loop: regex without g flag, exec always returns first match
const str = 'abc 123 def 456';
const regex = /\d+/; // No g flag!
let match;
// This loops forever because exec always returns the same first match
// while ((match = regex.exec(str)) !== null) { ... }
// DANGER: Infinite loop: regex created inside loop (new lastIndex each time)
const str = 'abc 123 def 456';
let match;
// Each iteration creates a new regex with lastIndex = 0
// while ((match = /\d+/g.exec(str)) !== null) { ... }
Safe pattern:
// Correct: regex declared outside the loop with g flag
const regex = /\d+/g;
const str = 'abc 123 def 456';
let match;
while ((match = regex.exec(str)) !== null) {
console.log(match[0]); // "123", then "456", then loop ends
}
Manually Controlling lastIndex
You can set lastIndex manually to start searching from a specific position:
const regex = /\w+/g;
const str = 'Hello beautiful world';
regex.lastIndex = 6; // Start after "Hello "
const match = regex.exec(str);
console.log(match[0]); // "beautiful"
console.log(match.index); // 6
console.log(regex.lastIndex); // 15
exec() with the y (Sticky) Flag
With the y flag, exec() matches only at the exact lastIndex position:
const regex = /\d+/y;
const str = '123abc456';
regex.lastIndex = 0;
console.log(regex.exec(str)); // ['123', index: 0, input: '123abc456', groups: undefined]
// lastIndex is now 3
console.log(regex.exec(str)); // null ("abc" at position 3, not digits)
// lastIndex reset to 0
console.log(regex.lastIndex); // 0
exec() with the d (hasIndices) Flag
The d flag (ES2022) adds an indices property to the match result, providing start and end positions for each group:
const regex = /(?<year>\d{4})-(?<month>\d{2})/d;
const str = 'Date: 2024-03';
const match = regex.exec(str);
console.log(match.indices[0]); // [6, 13] (full match start/end)
console.log(match.indices[1]); // [6, 10] (first group (year))
console.log(match.indices[2]); // [11, 13] (second group (month))
console.log(match.indices.groups.year); // [6, 10]
console.log(match.indices.groups.month); // [11, 13]
When to Use exec() vs. matchAll()
const str = 'a1 b2 c3';
const regex = /([a-z])(\d)/g;
// Modern approach (matchAll (cleaner, no state mutation)9
for (const match of str.matchAll(regex)) {
console.log(`${match[1]}=${match[2]}`);
}
// Traditional approach (exec (more control, but stateful)9
let match;
while ((match = regex.exec(str)) !== null) {
console.log(`${match[1]}=${match[2]}`);
}
// Both output: a=1, b=2, c=3
Use matchAll() when you simply need to iterate over all matches. Use exec() when you need manual control over the matching process, such as conditionally stopping early, modifying lastIndex, or using the y flag for positional matching.
regexp.test(str): Boolean Match Test
The test() method is the simplest regex method. It returns true if the pattern matches anywhere in the string, and false otherwise.
Basic Usage
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
console.log(emailRegex.test('user@example.com')); // true
console.log(emailRegex.test('not-an-email')); // false
console.log(emailRegex.test('')); // false
const hasDigit = /\d/;
console.log(hasDigit.test('hello')); // false
console.log(hasDigit.test('hello123')); // true
console.log(hasDigit.test('42')); // true
The g Flag Trap with test()
When test() is used with a regex that has the g flag, it updates lastIndex after each call. This leads to alternating results that confuse many developers:
const regex = /hello/g;
console.log(regex.test('hello world')); // true (lastIndex becomes 5)
console.log(regex.test('hello world')); // false (searching from index 5, no match)
console.log(regex.test('hello world')); // true (lastIndex was reset to 0)
console.log(regex.test('hello world')); // false (same cycle)
This is one of the most common regex bugs in JavaScript.
The fix: do not use the g flag with test() unless you intentionally want stateful behavior:
// Correct for simple boolean checks: no g flag
const regex = /hello/;
console.log(regex.test('hello world')); // true
console.log(regex.test('hello world')); // true (always consistent)
Using test() for Validation
test() is ideal for validation functions:
function isValidPassword(password) {
const hasMinLength = password.length >= 8;
const hasUppercase = /[A-Z]/.test(password);
const hasLowercase = /[a-z]/.test(password);
const hasDigit = /\d/.test(password);
const hasSpecial = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password);
return hasMinLength && hasUppercase && hasLowercase && hasDigit && hasSpecial;
}
console.log(isValidPassword('Hello123!')); // true
console.log(isValidPassword('hello')); // false
console.log(isValidPassword('HELLO123!')); // false (no lowercase)
function isValidHexColor(color) {
return /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(color);
}
console.log(isValidHexColor('#fff')); // true
console.log(isValidHexColor('#1a2b3c')); // true
console.log(isValidHexColor('#xyz')); // false
console.log(isValidHexColor('123456')); // false (missing #)
test() vs. search() vs. match()
When you only need a boolean answer, test() is the most efficient choice:
const str = 'The year is 2024';
const regex = /\d{4}/;
// All of these check if the pattern exists, but differently:
console.log(regex.test(str)); // true (simplest, most efficient)
console.log(str.search(regex) !== -1); // true (returns index, extra comparison)
console.log(str.match(regex) !== null); // true (creates match object, wasteful)
// For a simple "does it match?" question, always use test()
Complete Method Comparison
Here is a reference table covering all regex-related methods:
| Method | Called On | Returns | Flags Affected | Best For |
|---|---|---|---|---|
str.match(regex) | String | Array or null | g changes behavior | Quick match: single (no g) or all matches (g) |
str.matchAll(regex) | String | Iterator | Requires g | All matches with full group details |
str.search(regex) | String | Index or -1 | Ignores g, y | Finding position of first match |
str.replace(regex, repl) | String | New string | g for replace all | Replacing first or all matches |
str.replaceAll(regex, repl) | String | New string | Requires g | Replacing all (clearer intent) |
str.split(regex) | String | Array | Ignores g, y | Splitting by pattern |
regex.exec(str) | RegExp | Array or null | g/y update lastIndex | Manual iteration, positional matching |
regex.test(str) | RegExp | true/false | g/y update lastIndex | Boolean validation |
Decision Guide
Use this flowchart to pick the right method:
- Do you only need to know if there is a match? Use
regex.test(str) - Do you need the position of the first match? Use
str.search(regex) - Do you need the first match with groups? Use
str.match(regex)withoutg - Do you need all matches as simple strings? Use
str.match(regex)withg - Do you need all matches with groups? Use
str.matchAll(regex) - Do you need to replace matches? Use
str.replace()orstr.replaceAll() - Do you need to split by a pattern? Use
str.split(regex) - Do you need manual control over matching? Use
regex.exec(str)withgory
Be mindful of lastIndex when reusing a regex with the g or y flag across multiple exec() or test() calls on different strings. The stateful nature of lastIndex is the single most common source of regex bugs in JavaScript. Either reset it manually (regex.lastIndex = 0) or create a new regex for each use.
const regex = /\d+/g;
// First string
regex.exec('abc 123'); // match at index 4, lastIndex = 7
// Second string - BUG: lastIndex is still 7 from the previous call
regex.exec('xyz 456'); // null! Searching from position 7 in a shorter string
// Fix: reset lastIndex before reuse
regex.lastIndex = 0;
regex.exec('xyz 456'); // ["456"] (correct)