RegExp.prototype.sticky
Returns whether the regular expression was created with the y flag.
get RegExp.prototype.sticky- Added in JavaScript ES2015 (ES6)
Return value
booleantrue if the y flag is set, false otherwise.
Errors
TypeError if accessed on a value that is not a RegExp object (other than RegExp.prototype itself).
Changelog
- PHP ES2015The y flag and the sticky accessor were introduced.
- PHP ES2020String.prototype.matchAll() and replaceAll() throw a TypeError for a sticky regex that is not also global.
sticky reports whether the y flag is set. That flag makes a regex anchor to lastIndex: the match must begin at exactly that offset, or it fails outright. A global regex, by contrast, scans forward until it finds a match somewhere.
Why it exists
Sticky matching is what makes hand-written lexers correct and fast. Set lastIndex to the current position, try each token pattern in priority order, and take the first that matches. Because the engine never scans ahead, a failed rule costs one attempt at one offset rather than a sweep to the end of the input — and you can be sure the token you got starts where you expected, with no silently skipped characters.
The gotchas
On failure, lastIndex is reset to 0, so a tokenizer must assign it before every single call rather than relying on the previous one. matchAll() and replaceAll() still demand the g flag; y alone throws. And when both g and y are set, sticky wins for exec() and test().
Example
When to use it
- Write a tokenizer that consumes input strictly left to right
- Parse a fixed-format record field by field without accidental skipping
- Assert that a pattern matches at a known offset rather than anywhere later
Frequently asked questions
What is the difference between the sticky and global flags?
A global regex searches forward from lastIndex for a match anywhere in the remaining string; a sticky regex requires the match to start exactly at lastIndex and returns null otherwise. Both update lastIndex on success.
Why does my sticky regex only match once?
Because a failed match resets lastIndex to 0, and the next call then tries the start of the string again. Set re.lastIndex explicitly before every exec() rather than relying on the previous call to leave it in place.
Is a sticky regex the same as anchoring with ^?
No. ^ anchors to the start of the string (or of a line with the m flag), while y anchors to whatever lastIndex currently holds. That is what lets a tokenizer step through the input one token at a time.
More examples
A tokenizer built on sticky rules
Each rule is tried at exactly the current position, so unrecognised input raises an error instead of being silently skipped. Assigning lastIndex inside the loop is mandatory because a failed rule zeroes it.
const rules = [["num", /\d+/y], ["op", /[+\-*/]/y], ["ws", /\s+/y]];
const src = "12 + 7";
let pos = 0;
const tokens = [];
outer: while (pos < src.length) {
for (const [kind, re] of rules) {
re.lastIndex = pos; // must be set every time
const m = re.exec(src);
if (m) { if (kind !== "ws") tokens.push([kind, m[0]]); pos = re.lastIndex; continue outer; }
}
throw new SyntaxError(`unexpected character at ${pos}`);
}
console.log(tokens);Sticky versus global on failure
Given the same start offset the global regex finds the digits at index 2 while the sticky one gives up immediately. matchAll still requires g even when y is present, so a y-only regex throws.
const s = "ab12";
const g = /\d+/g, y = /\d+/y;
g.lastIndex = 0; y.lastIndex = 0;
console.log(g.exec(s)[0], g.lastIndex); // "12" 4 - scanned ahead
console.log(y.exec(s), y.lastIndex); // null 0 - no match at 0
console.log("aaa".match(/a/gy)); // [ "a", "a", "a" ]
try { "aaa".matchAll(/a/y); } catch (e) { console.log(e.name); }
Discussion