Regular Expressions: Stop Copy-Pasting Patterns You Can't Read · 第 3 / 6 节

Lesson 03: Saying how many times

Lesson objectives:

  • Repeat the preceding item with *, +, and ?.
  • Fix an exact or bounded count with {n} and {n,m}.
  • Say what a quantifier attaches to (the single item right before it).

Prerequisites: Lessons 01-02 (literals, character classes, \d) | Previous << 02 | Next 04 >>

One class matches one character — but data comes in runs

Last lesson every class matched exactly one character. Real data doesn't arrive one character at a time: a ZIP code is five digits, a username is a run of word characters, a phone number is a bunch of digits with maybe a dash. If a class only matches one spot, how do you match "a whole run of them"? That is the entire job of quantifiers — the metacharacters that say how many times the thing before them repeats. Miss which "thing before them" a quantifier grabs, and your counts land in the wrong place.

Explanation

A quantifier comes right after an item and controls how many times that item repeats. There are three you will reach for constantly1:

  • * — the preceding item, 0 or more times. /bo*/ matches "b" (zero "o"s) and "boooo".
  • + — the preceding item, 1 or more times. /a+/ matches the one "a" in "candy" and all of them in "caaandy"1.
  • ? — the preceding item, 0 or 1 time (optional). /colou?r/ matches both "color" and "colour", because the "u" is optional.

When you need an exact or bounded number, use braces1:

  • {n} — exactly n times. /a{2}/ matches "aa".
  • {n,m} — at least n and at most m times. /a{1,3}/ matches one to three "a"s.
  • {n,}n or more times, with no upper limit.

Here is the detail that trips people up: a quantifier attaches to the single item immediately before it, not to the whole pattern. In /ab+/, the + applies only to "b", so it matches "ab", "abb", "abbb" — one "a" followed by one-or-more "b". The "a" is not repeated. That "item before it" can be a literal, a character class, or (a later lesson) a group — but it is always just the one token to the left.

A word of warning that pays off next lesson: * and + are greedy — they grab as many repeats as they can. /a+/ on "caaandy" takes all three "a"s, not just one. We will make that behavior (and how to reverse it) the whole point of Lesson 06.

Worked example (follow along)

You want to match a time like "09:30" — two digits, a colon, two digits. Build it from a class plus counts:

Step by step: \d{2} means exactly two digits; the : is a literal colon; the second \d{2} is another two digits. "09:30" fits the shape exactly, so test is true. "9:30" fails because {2} demanded two digits before the colon and only one is present. Each quantifier attached to the \d right before it — not to the colon, not to the whole pattern.

Your turn (faded example)

You want to match a hashtag: a # followed by one or more word characters (so #a and #regex101 both match, but a lone # does not). Fill in the quantifier:

Answer: the blank is +, giving /#\w+/. The # is a literal, and \w+ means "one or more word characters." Because + requires at least one, a bare "#" fails (zero word characters after it), while "#regex101" matches. If you had used * instead, a lone "#" would have matched too, since * allows zero — which is exactly the "0 or more vs 1 or more" choice you now get to make on purpose.

Summary + what's next

Quantifiers say how many times the item before them repeats: * (0+), + (1+), ? (0 or 1), {n} (exactly n), {n,m} (between n and m). Each attaches to the single token on its left, and */+ are greedy by default. You can now match runs of characters with a controlled length. But a pattern like /\d{5}/ still matches five digits found anywhere inside a longer string — it can't yet say "and nothing else around them." Next we add anchors, which pin a match to the start, the end, or a word boundary.

Footnotes

  1. MDN: Quantifiers — https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Quantifiers 2 3

Exercises

Exercise 1 · Level 1 (warm-up)

In your console, define const s = "aaa bb c";. Predict, then run: s.match(/a+/), s.match(/b*/), and s.match(/z*/). Pay attention to what * returns when there is nothing to match.

Done criteria · checked locally
Exercise 2 · Level 2 (advanced)

Write a pattern for a US-style ZIP+4 like "90210-1234": five digits, a hyphen, then four digits. Test it against "90210-1234" (should pass) and "90210" (should fail). Then explain why /\d+-\d+/ is a worse pattern for this.

Done criteria · checked locally