正则表达式
在 JS
中,正则表达式与其他对象类型相似,有两种方式创建正则表达式:
- 正则表达式字面量
- 创建
RegExp
对象实例
在使用字面量的时候正则表达式用 /
分割:
const reg = /text/
如果创建 RegExp
对象实例则可以省略掉:
const reg = new RegExp('test')
在实际开发中推荐使用字面量的方式创建正则表达式,当需要在运行时动态创建字符串来构建正则表达式的时候使用构造函数的方式
修饰符
正则表达式中有 5 个修饰符:
- i
- 对大小写不敏感,例如
/test/i
,可以匹配test
、Test
等等
- 对大小写不敏感,例如
- g
- 查找所有匹配项,不会再找到第一个匹配项之后就停止查找
- m
- 允许多行匹配,对获取
textarea
元素value
值很有用
- 允许多行匹配,对获取
- y
- 开启粘连匹配,正则表达式执行粘连匹配时试图从最后一个匹配的位置开始
- u
- 允许使用
Unicode
点转义字符(\u{...}
)
- 允许使用
在定义字面量的时候修饰符写在后面:/test/ig
在使用构造函数的时候将修饰符作为第二个参数传给构造函数:new RegExp('test', 'ig')
匹配字符集
通常我们不需要像 /test/ig
这样匹配字符串,只想匹配一组有限的字符集中的字符,所以可以将我们希望匹配的字符集放在 []
中
[abc]
表示我们希望匹配 a、b、c
中任意一个字符
如果想反过来匹配除了 a、b、c
外其他字符,可以这样:[^abc]
如果想匹配的字符太多,可以使用 -
:[a-m]
匹配 a
到 m
中所有字符
如果除了 a-m
之间的字符,还想匹配 z
,可以加个逗号:[a-m,z]
就可以了
转义字符
我们知道有很多操作符,比如 $
、^
等等,如果使用正则表达式匹配这些字符而又不会被当成操作符呢?
使用转义字符就可以了:\$
匹配一个 $
字符
起止符号
如 ^test
匹配的是 test
出现在字符串的开头
$
表示字符串的结束:test$
匹配的是 test
出现在字符串的末尾
重复出现
假如你想匹配任意数量的相同字符可以试试以下几种方式:
- ?
- (指定字符可以出现 0 或 1 次)在字符串后面加 ?,
t?est
可以同时匹配test
和est
- (指定字符可以出现 0 或 1 次)在字符串后面加 ?,
- +
- (指定字符必须出现 1 或多次),
/t+est/
可匹配test、ttest、tttest
等
- (指定字符必须出现 1 或多次),
- *
- (指定字符出现 0 次或 1 次或多次),如
t*est
可以匹配test、ttest、est
等
- (指定字符出现 0 次或 1 次或多次),如
- {}
- 使用{}指定重复次数,
t{4}est
就是匹配ttttest
- 还可以指定重复次数范围,
t{4,10}est
匹配的是 4 到 10 个连续的t
接上est
- 还可以指定开放区间,
t{4,}est
可以指定 4 或更多个连续的t
接上est
- 使用{}指定重复次数,
这些运算符都可以是贪婪或者非贪婪的,默认是贪婪模式,可以匹配所有可能的字符,在运算符后面加?,比如 a+?
会使得运算为非贪婪模式,只进行最小限度的匹配
比如一个字符串 aaa
,/a+?/
只会匹配一个 a
,而 /a+/
会匹配三个 a
预定义字符集
预定义元字符 | 匹配的字符串 |
---|---|
\t | 水平制表符 |
[\b] | 退格 |
\v | 垂直制表符 |
\f | 换页符 |
\r | 回车符 |
\n | 换行符 |
\cA:\cZ | 控制字符 |
\u0000:\uFFFF | 十六进制 Unicode 码 |
\x00:\xFF | 十六进制 ASCII 码 |
. | 匹配除换行字符(\n 、\r 、\u2028 和\u2029 )之外的任意字符 |
\d | 匹配任意十进制数字,等价于[0-9] |
\D | 匹配除了十进制数字外的任意字符,等价于[^0-9] |
\w | 匹配任何字母,数字和下划线,等价于[A-Za-z0-9_] |
\W | 匹配除了字母,数字和下划线之外的字符,等价于[^A-Za-z0-9_] |
\s | 匹配任意空白字符(包括空格,制表符,换页符等等) |
\S | 匹配除了空白字符外的任意字符 |
\b | 匹配一个单词边界,即字与空格间的位置 |
\B | 匹配非单词边界(单词内部) |
分组
如果想对一组术语使用操作符,可以用圆括号进行分组,这与数学表达式类似,/(ab)+/
匹配多个连续的 ab
字符串
或
|表示或,/a|b/
可以匹配 a
或者 b
,/(ab)+|(cd)+/
可以匹配一个或多个 ab
或 cd
反向引用
例如正则表达式 /^([dtn])a\1/
匹配的内容是以字母 d,t,n
开头,其后连续字母 a
,在连接第一个分组中捕获的内容
一个非常重要的一点是:上面这个匹配规则并不与 /[dtn]a[dtn]/
相同,因为 a
后面连接的字母并不是任意的 b,t,n
而必须是与第一个匹配结果完全相同的才行,所以 \1
的结果只有在运行的时候才能确定
在匹配标签元素的时候,反向引用是很有用的:
/<(\w+)>(.+)<\/\1>/
这个正则表达式可以匹配像 <h1>123</h1>
这样简单的元素,如果没有反向引用,也许就无法匹配了,因为根本没办法知道与起始标记相匹配的结束标记是什么
正则表达式的编译
不管是通过字面量还是构造函数创建的正则表达式,都可以使用 test
方法来编译:
const reg1 = /test/gi
const reg2 = new RegExp('test', 'ig')
const contentText = 'dtaysdasdteststest'
console.log(reg1.test(contentText))
console.log(reg2.test(contentText))
下面来举个根据标签名查找元素的例子:
<div class="div1">123</div>
<div class="div2">456</div>
<span class="span1">789</span>
function findClassByTagName(className, tagName) {
const eleArr = document.getElementsByTagName(tagName || '*')
const regex = new RegExp(`(^|\\s)${className}(\\s|$)`)
const results = []
for (let i = 0, ele; (ele = eleArr[i++]); ) {
if (regex.test(ele.className)) results.push(ele)
}
return results
}
捕获匹配片段
举个例子:
<div id="div1" style="transform:translateY(15px);"></div>
function getTranslateY(ele) {
const transformValue = ele.style.transform
if (transformValue) {
const match = transformValue.match(/translateY\(([^\)]+)\)/)
return match ? match[1] : ''
}
return ''
}
const div1 = document.getElementById('div1')
console.log(getTranslateY(div1)) // 15px
在正则表达式中使用圆括号定义捕获,使用 match
方法匹配时,使用局部正则表达式可以返回一个数组,数组中包含了全部匹配内容以及操作中的全部捕获结果
使用全局表达式进行匹配
在 match
方法中使用局部表达式是可以正确的返回捕获结果的,但如果使用 g
修饰符,那么返回的就不仅是第一个匹配结果,而是全部的匹配结果,但是不会返回捕获的结果
可以使用 exec
方法,可以对一个正则表达式多次调用 exec
方法,每次调用可以返回下一个匹配的结果
const html = '<div class="test"><b>hello</b><i>world</i></div>'
const tag = /<(\/?)(\w+)([^>]*?)>/g
let match,
num = 0
while ((match = tag.exec(html)) !== null) {
console.log(match.length)
num++
}
console.log(num)
首先我们来看一下这里的正则表达式:/<(\/?)(\w+)([^>]*?)>/g
(\/?)
是匹配 0 到 1 个 /
,(\w+)
匹配一到无限个任意字母数字和下划线,([^>]*?)
匹配的是除了 >
之外的其他字符,比如在这里就匹配到了:class="test"
,其实就是匹配标签中的属性
这样一共调用了 6 次 exec
方法,因为一共匹配到了六个标签,因为我们使用了 3 对圆括号,所以每次调用 exec
返回的数组长度为 4,比如第一次调用 exec
返回的数组是:
const a = ['<div class="test">', '', 'div', ' class="test"']
第一个元素是匹配到的整个字符串,后面三个才是匹捕获的字符串
捕获的引用
可以使用反向引用来匹配 HTML
标记内容
const html = '<b class="hello">hello</b><i>world</i>'
const pattern = /<(\w+)([^>]*)>(.*?)<\/\1>/g
const match = pattern.exec(html)
console.log(match)
这里 \1
指向的是表达式中的 第一个捕获 ,在本例中捕获到的是标记的名称,使用第一个捕获的内容匹配对应的结束标记,但这种先给发是有缺点的,当标签中又嵌套了一个相同的标签,那就会匹配到错误的结果了
我们还可以使用字符串的 replace
方法对替代字符串内获取捕获,而不是使用反向引用
console.log('fontFamily'.replace(/([A-Z])/g, '-$1').toLowerCase()) // font-family
在这段代码中,第一个捕获的值(大写字母 F)通过替代字符($1
)进行引用,这种方式可以实现在不知道替代值时定义替换规则,直到匹配运行之前还不知道需要替代的值
未捕获分组
在正则表达式中圆括号不仅表示捕获,也表示分组,但是当一个分组过多的正则表达式就会产生很多不必要的捕获
假如有下面一个正则表达式:
const pattern = /((well-)+)play/
在这里,前缀 well-
可以在 play
前面出现一次或者多次,并且捕获整个前缀,这个表达式需要两对圆括号,但显然我们只需要捕获一个就好了,于是可以改成:
const pattern = /((?:well-)+)play/
这样写只有外层圆括号会创建捕获,内层圆括号变成一个被动子表达式
利用函数进行替换
正则表达式同样也可以应用在 replace
方法中:
'dsdadadg'.replace(/[a-z]/g, 'x')
返回的结果是 xxxxxxxg
,当然,这绝不是 replace
最终重要的特性,而且支持替换函数作为参数,当第二个参数为函数的时候,对每一个匹配到的值都会调用一遍
- 全文匹配
- 匹配时的捕获
- 在原始字符串匹配的索引
- 源字符串
- 从函数返回的值作为替换值
这在运行的时候提供了巨大的余地来判断应该替换的字符串,包括匹配的信息,比如将 -
连接的单词替换为驼峰式字符串:
const upperLetter = (all, letter) => letter.toUpperCase()
console.log('upper-letter'.replace(/-(\w)/g, upperLetter)) // upperLetter
正则表达式常见应用
匹配换行
const html = '<b>hello</b>\n<i>world</i>'
console.log(/.*/.exec(html)[0]) // <b>hello</b>
console.log(/[\S\s]*/.exec(html)[0]) // <b>hello</b>\n<i>world</i>
console.log(/(?:.|\s)/.exec(html)[0]) // <b>hello</b>\n<i>world</i>
这里我们使用了三种方法,第一种方法匹配失败,后两种成功,/(?:.|\s)/
使用 .
匹配除换行外所有字符,通过 \s
匹配所有字符包括换行,集合的结果是所有字符,当然 /[\S\s]*/
因为简洁高效被认为是最优解
匹配 Unicode 字符
想要匹配 Unicode 字符很简单:
const matchAll = /[\w\u0080-\uFFFF_-]+/
除了使用 \w
匹配正常的字符之外,还要使用 \u0080-\uFFFF
匹配 \u0080
以上的全部字符