JAVASCRIPT / REGULAR EXPRESSIONS
Regular expressions and literal patterns
Write regex literals, separate literal characters from metacharacters, escape them, and build runtime patterns with new RegExp without losing backslashes.
What you will learn
- Write a fixed pattern as /pattern/ and call .test(str) for a yes/no substring match
- Escape . * + ? ^ $ { } ( ) | [ ] \ / with a backslash to match them literally
- Double every backslash in new RegExp strings, or wrap the pattern in String.raw
- Escape interpolated text before new RegExp so metacharacters cannot break the pattern
Understanding Regular expressions and literal patterns
A regular expression literal is a pattern written between two forward slashes, and the JavaScript parser treats it as its own kind of value, the same way it treats 42 or "hi". Evaluating /cat/ produces a RegExp object whose .source is the text cat, and that object is what methods like test and search actually consume. Inside the slashes you are no longer writing JavaScript but a separate small language read by the regex engine, so quotes, spaces, and operators there mean nothing like they do in the surrounding code. Matching is a search by default: /cat/.test("concatenate") is true because the engine only has to find the pattern somewhere in the string.
In that pattern language most characters are literal, meaning they stand for exactly themselves. About a dozen do not: . * + ? ^ $ { } ( ) | [ ] \ and the / that would end the literal early. The backslash is the switch between the two groups and it works in both directions: \. demotes the special dot to an ordinary dot, while \d promotes an ordinary d into "any digit". Treating the backslash as a toggle rather than as "escape the weird stuff" explains why /9.99/ happily matches 9x99, and why a forgotten backslash usually gives you a pattern that is too permissive instead of one that fails loudly.
The RegExp constructor is the other way to build a pattern, and the real difference is when the pattern text gets decided. A literal is fixed while the file is parsed, so a malformed one is a syntax error before any code runs; new RegExp(str) is compiled when that line executes, which lets you splice in a variable but turns a broken pattern into a runtime SyntaxError. The trap is that the string literal is processed first, so "\d" loses its backslash to JavaScript's string rules and reaches the engine as a plain d, which is why constructor patterns need doubled backslashes or String.raw. Use the literal whenever the pattern is known ahead of time, and the constructor only when part of it is genuinely dynamic.
Nothing further.
// A regular expression literal: the pattern sits between the slashes
const bare = /cat/;
console.log(bare.test("concatenate")); // found anywhere in the string
console.log(bare.test("Cat")); // matching is case sensitive
// Most characters are literal; a few are metacharacters
const anyChar = /c.t/;
const realDot = /c\.t/;
console.log(anyChar.test("cut")); // . matches any single character
console.log(realDot.test("cut")); // now the dot must be a real dot
console.log(realDot.test("abc.txt"));
// The literal evaluates to an object that carries the pattern text
console.log(realDot.source, typeof realDot, realDot instanceof RegExp);Inside a pattern every character is either literal or special, and the backslash is the switch that moves a character between those two roles.
Worked examples
Backslashes twice in the constructor
Shows why a pattern written as a string needs doubled backslashes, and what the engine receives when it does not.
const fromLiteral = /\d+\.\d+/;
const wrong = new RegExp("\d+\.\d+");
const right = new RegExp("\\d+\\.\\d+");
const raw = new RegExp(String.raw`\d+\.\d+`);
console.log(wrong.source);
console.log(right.source, right.source === fromLiteral.source);
console.log(raw.source === fromLiteral.source);
console.log(wrong.test("3.14"), right.test("3.14"));Example explained
Line 1\d is not a recognized string escape, so JavaScript drops the backslash and the engine only ever sees d+.d+.
Line 2Doubling each backslash gets one real backslash through to the pattern, so right.source is character for character the same as the literal's source.
Line 3String.raw leaves backslashes untouched, which makes the tagged template read exactly like the literal.
Line 4wrong.test("3.14") is false because that pattern now hunts for the letter d, and "3.14" contains no d.
String argument or pattern
Demonstrates that string arguments to string methods are always literal, while a pattern reinterprets the same characters.
const path = "src/app.test.js";
console.log(path.split(".").length);
console.log(path.split(/./).length);
console.log(path.split(/\./).length);
console.log(path.search(/\.test\./));Example explained
Line 1split(".") receives a string, and string arguments are never patterns, so it cuts on the two real dots and returns 3 pieces.
Line 2split(/./) receives a pattern whose dot matches every character, so all 15 characters act as separators and 16 empty strings come back.
Line 3Escaping the dot restores the intended meaning: /\./ behaves exactly like the string ".".
Line 4search returns the index where the pattern first matches, 7 here, and -1 when there is no match at all.
Building a pattern from user input
Shows how unescaped input can make new RegExp throw, and how escaping it produces a pattern that matches the text as typed.
const term = "c++";
try {
new RegExp(term);
} catch (err) {
console.log(err.name);
}
const escaped = term.replace(/[.*+?^$(){}|[\]\\]/g, "\\$&");
console.log(escaped);
const re = new RegExp(escaped);
console.log(re.test("wrote c++ for years"), re.test("wrote cpp for years"));Example explained
Line 1new RegExp("c++") throws because the second + is read as a quantifier applied to a quantifier, which the pattern language does not allow.
Line 2The replace puts a backslash in front of each metacharacter, and $& in the replacement stands for whatever was matched.
Line 3The resulting source is c\+\+, so both plus signs are ordinary characters that must be present.
Line 4The escaped pattern matches the real text and rejects "cpp", proving the pluses are required rather than optional repetition.
Important notes
// is not an empty pattern, it starts a comment; use new RegExp("") if you need one, and note its .source reads (?:).
Every evaluation of a literal creates a new object, so /a/ === /a/ is false; compare .source when you need to know two patterns are the same.
Common mistakes
Leaving the dot unescaped in patterns like /app.js/ or /9.99/: they also match appXjs and 9x99, so invalid input passes as valid and the bug never raises an error.
Copying a literal's backslashes straight into a string: new RegExp("\d") searches for the letter d, so the pattern silently never matches a digit.
Dropping user input into new RegExp unescaped: a term like c++ or a lone ( throws SyntaxError at runtime and breaks the search instead of finding nothing.
Try it yourself
Change, predict, then run
In the browser console, build a pattern that matches the literal text (price + tax) so that it matches "Total = (price + tax)" but not "Total = price + tax". Then create the same pattern with new RegExp from a string and confirm both .source values are identical.
Open the JavaScript workspaceCheck your understanding
Which of these strings does the pattern /(2+2)/ match?
- "(2+2)"
- "2+2"
- "222"
- None of them
Show answer
The parentheses group and the + means "one or more of the preceding 2", so the pattern needs two adjacent 2 characters and never requires a parenthesis or a plus sign; "222" supplies them. "(2+2)" is tempting because it looks like a copy of the pattern text, but the engine reads none of those characters literally, and the string has no two 2s in a row. Matching that text literally takes /\(2\+2\)/.