持续创作,加速成长!这是我参与「掘金日新计划 · 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
中可以找到我们的命名和对应的值。