Download Cheat sheet PDF 12 pages · syntax, editors, patterns, Unicode, performance, debugging
Guide

Regex in JavaScript

JavaScript's regex API has a few sharp edges that catch people. Here's what to use when, and what to avoid.

Literal vs constructor

const a = /\d+/g;             // literal
const b = new RegExp("\\d+", "g");  // constructor

Literals are parsed once at script load. Constructors run at evaluation time, so use them when the pattern comes from a variable. Don't forget: in the constructor form, the string sees backslashes first — you need to double them.

Test vs match vs matchAll

regex.test(str) returns true/false. Fast — use it when you just need to know if there's a match.

str.match(regex):

  • Without /g: returns the first match with capture groups, or null.
  • With /g: returns an array of full-match strings only — capture groups are lost.

str.matchAll(regex) requires /g, returns an iterator of full match objects (with groups). Use this when you need both global iteration and capture groups.

const text = "phone: 555-1234, fax: 555-5678";

// Just check
/\d{{3}}-\d{{4}}/.test(text);                      // true

// First match with groups
text.match(/(\d{{3}})-(\d{{4}})/);                  // ["555-1234", "555", "1234"]

// All matches as strings
text.match(/\d{{3}}-\d{{4}}/g);                     // ["555-1234", "555-5678"]

// All matches with groups
[...text.matchAll(/(\d{{3}})-(\d{{4}})/g)];         // [["555-1234", "555", "1234"], ...]

The lastIndex trap

A regex object with /g or /y remembers where it left off via lastIndex. Subsequent test or exec calls continue from there.

const re = /\d/g;
re.test("a1b");  // true, lastIndex now 2
re.test("a1b");  // false! Started search at index 2
re.test("a1b");  // true again — lastIndex reset to 0 on no-match

The fix: don't keep a stateful global regex if you're calling test repeatedly. Either reset lastIndex = 0, or drop the g flag.

Replace

str.replace(regex, replacement) with a global regex replaces every match. With a non-global, only the first.

Replacement strings have their own metacharacters:

$&    the whole match
$1, $2  capture groups
$<name> named group (ES2018+)
$$    a literal dollar sign
$`    text before the match
$'    text after the match

Replacement as function — runs once per match:

"hello world".replace(/(\w+)/g, (match, group1) => group1.toUpperCase());
// "HELLO WORLD"

String.split with regex

str.split(regex) splits the string by every regex match. Capture groups in the regex are included in the output array — useful for tokenizers.

"a-1-b-2-c".split(/-/);          // ["a", "1", "b", "2", "c"]
"a-1-b-2-c".split(/(-)/);        // ["a", "-", "1", "-", "b", "-", "2", "-", "c"]

Always use /u for non-ASCII

Without the u flag, JavaScript treats surrogate pairs (emoji, astral plane Unicode) as two characters. /.{{1}}/.test("👍") is true because . matched one of the two surrogate halves. /.{{1}}/u.test("👍") is false because . now matches the whole code point.

If you ever handle user-supplied text that might contain emoji or non-Latin scripts, default to u.

RegExp.escape is coming, but not yet

If you need to embed a literal string in a regex, you have to escape its metacharacters yourself:

function escape(s) {{
  return s.replace(/[.*+?^${{}}()|[\]\\]/g, '\\$&');
}}
new RegExp(escape(userInput));

A RegExp.escape static method is in the TC39 pipeline. For now, every project has its own escape helper.


← Back to guides