持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天,点击查看活动详情
介绍
正则表达式 (Regular Expression) 是用于匹配字符串中字符组合的模式。在 JavaScript 中,正则表达式也是对象。这些模式被用于 RegExp 的 exec 和 test方法,以及 String 的 match、matchAll、replace、search 和 split 方法。
为什么使用正式表达式?下面用一个示例说明:
问题:找出
aaa11bbb22ccc33ddd44字符串中的所有数字并添加到数组中,应该得到['11', '22', '33', '44']
不使用正则表达式的情况可能是像下面这样处理的:
const str = 'aaa11bbb22ccc33ddd44'
function getNumber(val) {
let arr = []
let tempNumber = ''
for(let i = 0; i < val.length; i++) {
if(!isNaN(parseFloat(val[i]))) {
// console.log(val[i])
tempNumber += val[i]
} else {
if(tempNumber) {
arr.push(tempNumber)
}
tempNumber = ''
}
}
// 兼容 字符串最后面是数字的情况
if(tempNumber) arr.push(tempNumber)
return arr
}
console.log(getNumber(str)) // ['11', '22', '33', '44']
用正则表达式来解决这个问题,代码则是下面这样的:
const reg = /\d+/g
const arr = str.match(reg)
console.log(arr) // ['11', '22', '33', '44']
可以看到只需要几行代码就能够得到想要的结果,使用正则表达式解决这个问题的话代码就很简洁。
日常开发中,正则表达式做的工作主要是:对字符串的 查找 替换 验证 分割等。
本文主要写了以下重点内容:
- 正则的创建
- 正则的匹配方式
- 元字符
- 字符集合
- 边界
- 分组
- 反向引用
- 匹配模式
- 命名分组
创建正则
字面量方式
格式:/ [规则] / [修饰符],用两条/ 创建正则,中间定义规则,如 \d+,结尾可以接一个修饰符,如 g 表示全局匹配
例如:
const reg = /\d+/g
构造函数方式
格式:new RegExp([规则], [修饰符])
例如:
const reg = new RegExp('\\d+', 'g')
注意:RegExp 创建正则表达式时,由于规则参数放在字符串中,所以涉及到带 \ 的规则时,都要在前面多加一个 \ 来进行转义。
正则的匹配方式
正则对象的方法
test
test() 方法执行一个检索,用来查看正则表达式与指定的字符串是否匹配。返回 true 或 false。
const str = '11abc11abc11'
const reg1 = new RegExp('abc')
console.log(reg1.test(str)) // true
如果正则表达式设置全局匹配 g 修饰符,每一次调用都会改变正则对象的 lastIndex 属性。下次再执行 test() 时,将会从 lastIndex 属性值处开始查找,自动忽略掉之前匹配过的。也就是说 lastIndex 会记录匹配的位置。
const str = '11abc11abc11'
const reg2 = new RegExp('abc', 'g')
console.log(reg2.test(str)) // true
console.log(reg2.lastIndex) // 5
console.log(reg2.test(str)) // true
console.log(reg2.lastIndex) // 10
console.log(reg2.test(str)) // false
console.log(reg2.lastIndex) // 0
exec
exec() 方法在一个指定字符串中执行一个搜索匹配。返回一个结果数组或null。
const str = '11abc11abc11'
const reg1 = new RegExp('abc')
console.log(reg1.exec(str))
console.log(reg1.exec(str))
console.log(reg1.exec(str))
不进行全局匹配时,每次返回的结果都一样,如下:
使用全局匹配时:
const str = '11abc11abc11'
const reg2 = new RegExp('abc', 'g')
console.log(reg2.exec(str))
console.log(reg2.exec(str))
console.log(reg2.lastIndex) // 10
console.log(reg2.exec(str))
匹配成功时,exec() 方法返回一个数组,并更新正则表达式对象的 lastIndex 属性。由上图可知,第三次匹配失败返回了 null,因为字符串中只有两个 abc 字符。全局匹配时,每次调用 exec() 都是基于当前的 lastIndex,第三次执行时从下标为 10 的地方开始查找,后面没有符合规则的字符了。匹配失败时,也会将 lastIndex 的值重置为 0 。
字符串的方法
| 方法 | 用途 |
|---|---|
| spilt | 切割字符串 |
| search | 查找 |
| match | 匹配 |
| replace | 查找替换 |
spilt
按照数字切换字符串返回一个数组:
const str = 'abc11de11f'
const reg = /\d+/
const res = str.split(reg)
console.log(res) // ['abc', 'de', 'f']
search
查找符合规则的元素,返回第一个匹配元素的索引值,会忽略全局属性。如果没有符合规则的返回-1。
const str = 'abc11de11f'
const reg = /\d+/
const res = str.search(reg)
console.log(res) // 3
console.log(str.search(/c/)) // 2
match
匹配符合正则的元素,放到数组中并返回。
const str = 'abc11de11f'
const reg = /\d+/
const res = str.match(reg)
console.log(res) // ['11', index: 3, input: 'abc11de11f', groups: undefined]
如果不是全局匹配的话,返回的数组第一项为匹配到的元素,还会有一些额外附加的数据,有点像 exec() 返回的数据。
全局匹配的情况下,返回一个正常的包含所有匹配项的数组,如下:
const str = 'abc11de11f'
const reg = /\d+/g
const res = str.match(reg)
console.log(res) // ['11', '11']
replace
查找符合规则的元素,替换成其他的内容,再返回处理后的字符串。
const str = 'abc11de11f'
const reg = /\d+/g
const res = str.replace(reg, '*')
console.log(res) // abc*de*f
元字符
元字符:指有特殊含义的非字母字符,如上文经常使用的 \d 。
大致可以分为以下几类:
- 字符相关
- 数量相关
- 位置相关
- 括号相关
字符相关
| 元字符 | 匹配元素 |
|---|---|
\w | 数字、字母、下划线 |
\W | 非数字、字母、下划线 |
\d | 数字 |
\D | 非数字 |
\s | 空格 |
\S | 非空格 |
. | 非\n、\r、\u2028、\u2029 |
举例:
const reg = /\w+/g
console.log(reg.test('aa111_1')) // true
console.log(reg.test('aa111_1~')) // false 包含波浪线
const reg = /\W+/g
console.log(reg.test('aa111_1')) // false
console.log(reg.test('aa111_1~')) // true
const reg = /./g
const str1 = `
` // 包含回车
const str2 = '12ddd'
console.log(reg.test(str1)) // false
console.log(reg.test(str2)) // true
const reg = /a.c/g
const str = 'abc'
console.log(reg.test(str)) // true
数量相关
| 元字符 | 匹配元素 |
|---|---|
{} | 设置匹配元素的次数 |
? | 匹配0或者1次 |
+ | 匹配1到无限多次 |
* | 匹配0到无限多次 |
{} 元字符有三种写法:
{3}匹配固定3次{3,5}匹配3到5次{1,}匹配1到无限次
const reg = /ab{1,}/g
const str = '111abbbc'
console.log(reg.test(str)) // true
?可以理解为{0,1}+可以理解为{1,}*可以理解为{0,}
// 匹配数字的正则
const reg = /\d?/
const reg1 = /\d+/
const reg2 = /\d*/
const reg3 = /\d{3,5}/
位置相关
| 元字符 | 匹配元素 |
|---|---|
^ | 字符串开始位置 |
$ | 字符串结束位置 |
\b | 边界符,匹配有边界的。边界是指非\w的,也就是非数字、字母、下划线的都属于边界 |
\B | 匹配没有边界的 |
举例:
匹配到第一个字符,将第一个字符变成 *:
const reg = /^\w/
const str = '12345'
console.log(str.replace(reg, '*')) // *2345
如果只有 ^ 符号,后面不接 \w,那么就会匹配第一个字符前的位置,如下:
const reg = /^/
const str = '12345'
console.log(str.replace(reg, '*')) // *12345
$ 也同理:
const reg = /\w$/ // 匹配最后一个字符
const reg = /$/ // 匹配最后一个字符后面的位置
下面字符串有两个 aa,要匹配到边界的 aa,而不是所有的aa
const reg = /aa/g
const str = '123aa12 aa bb'
console.log(str.match(reg)) // ['aa', 'aa'] 全都匹配到了
const reg2 = /\baa\b/g
console.log(str.match(reg2)) // ['aa'] 只匹配左右都有边界的
匹配左边无边界,右边有边界,长度为2的字符:
const reg = /\B\w{2}\b/g
const str = '123aa12 aa bbb'
console.log(str.match(reg)) // ['12', 'bb']
由上面的例子可以知道,第一个字符的左侧和最后一个字符的右侧也都属于边界。
括号相关
| 元字符 | 作用 |
|---|---|
{} | 上文说过的表示匹配次数 |
() | 使用()包裹的为子表达式。分组、提取值、替换,反向引用 |
[] | 字符集合 |
提取值
- 举例:使用
()提取值:
从字符串中提取出日期:
const str = '2060-12-10'
const reg = /\d{4}-\d{2}-\d{2}/
console.log(str.match(reg)) // ['2060-12-10', index: 0, input: '2060-12-10', groups: undefined]
不适用()元字符的话,提取出来的是整个字符串,加上()能够得到预期结果:
const str = '2060-12-10'
const reg = /(\d{4})-(\d{2})-(\d{2})/
console.log(str.match(reg)) // ['2060-12-10', '2060', '12', '10', index: 0, input: '2060-12-10', groups: undefined]
得到的结果是:['2060-12-10', '2060', '12', '10', index: 0, input: '2060-12-10', groups: undefined],可以通过数组下标找到这些值,也可以像下面这样:
console.log(RegExp.$1) // 2060
console.log(RegExp.$2) // 12
console.log(RegExp.$3) // 10
将匹配到的子表达式保存在了RegExp对象中,RegExp.$1表示保存的第一个子表达式,以此类推。
把年月日换成年/月/日格式显示 :
console.log(str.replace(reg, '$1/$2/$3')) // 2060/12/10
反向引用
- 反向引用可以调用捕获到的值,举例:
假设一个场景:css样式命名时,可以使用footer-box-item或者footer-box-item,但是- 和 _ 不能同时出现,比如 footer-box_item 这种写法是不允许的,下面用正则来匹配正确的格式:
const css = 'footer-box_item'
const reg = /\w+(_|-)\w+(\1)\w+/
console.log(reg.test(css)) // false
const css = 'footer-box-item'
const reg = /\w+(_|-)\w+(\1)\w+/
console.log(reg.test(css)) // true
(_|-) 表示 第一个字符的位置可以是 _ 或者 -,第二个字符的位置使用了 (\1),表示要匹配到和第一个一样的元素,所以就限制了在这个字符串中只能出现一种符号。
\1 指向 RegExp.$1,\2 指向 RegExp.$2,以此类推。
字符集合 []
使用 [] 表示一段字符的集合。
- 如:
AaBb[a-z][0-9] - 字符集合
[]中的^表示非,取反的意思。而不是上文中说的匹配开头位置。[^0-9]表示匹配非数字。 - 用字符集合
[]表示预设的元字符:[0-9]等价于\d[0-9A-Za-z_]等价于\w
一段内容在 ASCII码 中是连续的,才可以使用
-进行连接表示一个集合。
贪婪匹配与惰性匹配
- 贪婪匹配:尽可能多的匹配,默认
- 惰性匹配:尽可能少的匹配,需要使用
?元字符将正则改为惰性匹配
例如:/\d{2,4}/在条件允许的情况下,优先匹配4个数字。而 /\d{2,4}?/在条件允许的情况下,优先匹配2个数字。
匹配模式
| 匹配模式 | 含义 |
|---|---|
g | 全局匹配 |
i | 忽略大小写 |
m | 多行模式 |
s | 让 . 元字符可以匹配到换行 |
u | 匹配unicode编码 |
y | 粘性模式:匹配的字符需要是连续的 |
- 举例:多行模式
const str = `123
456
789
`
const reg = /^\w/g
console.log(str.replace(reg, '*')) // false
如图:只把第一行的第一个字符替换成了 *,使用 m 模式后,可以将三行的首个字符全部替换成 *
const reg = /^\w/gm
- 举例:匹配 unicode 编码
const str = 'a'
const reg = /\u{61}/gu
console.log(reg.test(str)) // true
a的 unicode 编码是61
命名分组
() 进行分组时,可以将每组命名,方便调用。
const str = '2060-12-10'
const reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
console.log(str.match(reg))
groups中可以找到我们的命名和对应的值。