match() and matchAll(), a RegExp lover's dream (and also a cool playground)
The JavaScript String Methods series
- Part 0 - toUpperCase() and toLowerCase()
- Part 1 - split() and join()
- Part 2 - slice(), substring(), substr()
- Part 3 - charAt(), charCodeAt(), fromCharCode(), at()
- Part 4 - concat() and repeat()
- Part 5 - search() and includes()
- Part 6 - indexOf() and lastIndexOf()
- Part 7 - endsWith() and startsWith()
- You are herePart 8 - match() and matchAll()
- Part 9 - replace() and replaceAll()
- Part 10 - trim(), trimEnd() and trimStart()
- Part 11 - padStart() and padEnd()
In this post, skip to:
match
match()
returns the result, in Array
form, of matching a string against the regular expression you pass as a parameter.
If you pass a string or number, it'll be converted into a regular expression under the hood, with new RegExp()
. Ideally you'll use a regular expression directly so you'll be able to better cover edge cases, though.
If you want to retrieve all matches and not just the first one, you can optionally use the RegExp global flag g
.
Here are some examples:
let string = "We're going on an adventure, Charlie!"
string.match(/C/)
//output: ['C', index: 29, input: "We're going on an adventure, Charlie", groups: undefined]
string.match('C')
//output: ['C', index: 29, input: "We're going on an adventure, Charlie", groups: undefined]
string.match(/[A-Z]/g)
//output: (2) ['W', 'C']
string.match(/[A-Z]/)
//output: ['W', index: 0, input: "We're going on an adventure, Charlie", groups: undefined]
string.match("!")
//output: null
string.match("e")
//output: ['e', index: 1, input: "We're going on an adventure, Charlie", groups: undefined]
string.match(/e/g)
//output: (5) ['e', 'e', 'e', 'e', 'e']
You can read more about RegExp.prototype[@@match] in the MDN docs, they have demos and examples too!
matchAll
It gets a little more complex here. It's not just an implementation of match()
with a global flag built-in - in fact, if you use a regular expression as a parameter and forget to include /g
then you'll get a TypeError. You may choose to pass something else as the parameter, but it will be implicitly converted to RegExp anyway.
This actually returns an iterator, in object form, so we can play around with it using for...of
, Array.from()
or array spreading.
Let's take a look and explore those returns:
let string = "We're going on an adventure, Charlie!"
// using an expression to find lowercase vowels followed by "n" then a space
let array1 = Array.from(string.matchAll(/([aeiou]n\s)/g))
console.log(array1)
//output: (2) [Array(2), Array(2)]
array1[0]
//output: ['on ', 'on ', index: 12, input: "We're going on an adventure, Charlie", groups: undefined]
array1[1]
//output: ['an ', 'an ', index: 15, input: "We're going on an adventure, Charlie", groups: undefined]
//------------
// now the expression finds any uppercase letter in the alphabet followed by a lowercase letter
let array2 = [...string.matchAll(/([A-Z][aeiou])/g)]
//output: [Array(2)]
array2[0]
//output: (2) ['We', 'We', index: 0, input: "We're going on an adventure, Charlie", groups: undefined]
//------------
//let's find lowercase vowels followed by "n" and loop through them
let array3 = string.matchAll(/([aeiou]n)/g))
for (entry of array3){
console.log(`"${entry[0]}" starting at string[${entry.index}]`)
}
//output:
/*
"in" starting at string[8]
"on" starting at string[12]
"an" starting at string[15]
"en" starting at string[21]
*/
matchAll capturing groups
matchAll()
also allows capturing groups
, which allows us to further classify and reuse the matches we've had.
Let's try something different this time.
We're going to find all uppercase characters optionally followed by something other than a vowel.
Then, we'll name two groups: uppercase
and notVowel
.
Finally, we'll loop through them, saying *crickets*
if notVowel
is undefined (meaning there was nothing found in that part of the search)
let string = "We're going on an adventure, Charlie!"
let dizzying = [...string.matchAll(/(?<uppercase>[A-Z])(?<notVowel>[^aeiou])*/g)]
console.log(dizzying)
//output: (2) [Array(3), Array(3)]
console.log(dizzying[0], dizzying[1])
//output:
// (3) ['W', 'W', undefined, index: 0, input: "We're going on an adventure, Charlie", groups: {…}]
// (3) ['Ch', 'C', 'h', index: 29, input: "We're going on an adventure, Charlie", groups: {…}]
// Oh look! Now groups are not "undefined"! Let's take a look inside:
console.log(dizzying[0].groups, dizzying[1].groups)
//output:
// {uppercase: 'W', notVowel: undefined}
// {uppercase: 'C', notVowel: 'h'}
for (entry of dizzying){
console.log(`Found ${entry.groups.uppercase} at ${entry.index}, followed by... ${entry.groups.notVowel === undefined ? '*crickets*' : entry.groups.notVowel}!`)
}
//output:
/*
Found W at 0, followed by... *crickets*!
Found C at 29, followed by... h!
*/
So yeah, it can be a little overwhelming but matchAll()
gives us tool to play around!
Time for practice!
Try this easy CodeWars kata if you've got the time:
There are excellent katas to practice at jskatas.org, too.