正则
基础知识
对比分析
与普通函数操作字符串来比较,正则表达式可以写出更简洁、功能强大的代码。
下面使用获取字符串中的所有数字来比较函数与正则的差异。
const str = 'dsfafa1214fdsfs1222'
console.time('fn')
const nums = [...str].filter(a => !Number.isNaN(Number.parseInt(a)))
console.log(nums.join(''))
console.timeEnd('fn')
const str = 'dsfafa1214fdsfs1222'
console.time('re')
const num = str.match(/\d/g).join('')
console.timeEnd('re')
创建正则
JS提供字面量与对象两种方式创建正则表达式
字面量创建
使用//包裹的字面量创建方式是推荐的作法,但它不能在其它使用变量
const str = 'mkimq'
const a = 'i'
console.log(eval(`/${a}/`).test(str))
console.log(/m/.test(str))
// a 变量时将不可以查询
const a = 'm'
console.log(/a/.test(str))
虽然可以使用 eval 转换为js语法来实现将变量解析到正则中,但是比较麻烦,所以有变量时建议使用下面的对象创建方式
const str = 'mkimq'
const a = 'm'
console.log(eval(`/${a}/`).test(str))
对象创建
当正则需要动态创建时使用对象方式
const str = 'mkimq'
const m = 'mk'
const reg = new RegExp(m)
console.log(reg.test(str))
选择符
|
这个符号带表选择修释符,也就是|
左右两侧有一个匹配到就可以。
const tel = '010-12345678'
console.log(tel.match(/(010|020)\-\d{7,8}/))
字符转义
转义用于改变字符的含义,用来对某个字符有多种语义时的处理。
假如有这样的场景,如果我们想通过正则查找/符号,但是 /在正则中有特殊的意义。如果写成///这会造成解析错误,所以要使用转义语法 ///来匹配。
字符边界
使用字符边界符用于控制匹配内容的开始与结束约定。
边界符 | 说明 |
---|---|
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束,忽略换行符 |
元子字符
元字符是正则表达式中的最小元素,只代表单一(一个)字符
字符列表
元字符 | 说明 | 示例 |
---|---|---|
\d | 匹配任意一个数字 | [0-9] |
\D | 与除了数字以外的任何一个字符匹配 | [^0-9] |
\w | 与任意一个英文字母,数字或下划线匹配 | [a-zA-Z_] |
\W | 除了字母,数字或下划线外与任何字符匹配 | [^a-zA-Z_] |
\s | 任意一个空白字符匹配,如空格,制表符\t,换行符\n | [\n\f\r\t\v] |
\S | 除了空白符外任意一个字符匹配 | [^\n\f\r\t\v] |
. | 匹配除换行符外的任意字符 |
可以使用 [\s\S] 或 [\d\D] 来匹配所有字符
模式修饰
正则表达式在执行时会按他们的默认执行方式进行,但有时候默认的处理方式总不能满足我们的需求,所以可以使用模式修正符更改默认方式。
修饰符 | 说明 |
---|---|
i | 不区分大小写字母的匹配 |
g | 全局搜索所有匹配内容 |
m | 视为多行 |
s | 视为单行忽略换行符,使用. 可以匹配所有字符 |
y | 从 regexp.lastIndex 开始匹配 |
u | 正确处理四个字符的 UTF-16 编码 |
lastIndex
RegExp对象lastIndex 属性可以返回或者设置正则表达式开始匹配的位置
- 必须结合g修饰符使用
- 对exec方法有效
- 匹配完成时,lastIndex会被重置为0
let mk = `mkimq在线文档,网址是mkimq.com`
let reg = /mkimq(.{2})/g
reg.lastIndex = 10 // 从索引10开始搜索
console.log(reg.exec(mk))
console.log(reg.lastIndex)
reg = /\p{sc=Han}/gu
while ((res = reg.exec(mk))) {
console.log(res[0])
}
原子表
在一组字符中匹配某个元字符,在正则表达式中通过元字符表来完成,就是放到[] (方括号)中。
原子表 | 说明 |
---|---|
[] | 只匹配其中的一个原子 |
[^] | 只匹配"除了"其中字符的任意一个原子 |
[0-9] | 匹配0-9任何一个数字 |
[a-z] | 匹配小写a-z任何一个字母 |
[A-Z] | 匹配大写A-Z任何一个字母 |
原子组
- 如果一次要匹配多个元子,可以通过元子组完成
- 原子组与原子表的差别在于原子组一次匹配多个元子,而原子表则是匹配任意一个字符
- 元字符组用 () 包裹
变量 | 说明 |
---|---|
0 | 匹配到的完整内容 |
1,2.... | 匹配到的原子组 |
index | 原字符串中的位置 |
input | 原字符串 |
groups | 命名分组 |
const str = 'mankeung.com'
const d = str.match(/man(keung)\.(com)/)
console.log(d)
// [
// 'mankeung.com',
// 'keung',
// 'com',
// index: 0,
// input: 'mankeung.com',
// groups: undefined
// ]
引用分组
\n在匹配时引用原子组,$n指在替换时使用匹配的组数据。下面将标签替换为p标签
const h = `
<h1>mkimq</h1>
<span>在线文档</span>
<h2>mkimq.com</h2>
`
const reg = /<(h[1-6])>([\s\S]*)<\/\1>/gi // \1引入原子组1
console.log(h.replace(reg, `<p>$2</p>`))
分组别名
如果希望返回的组数据更清晰,可以为原子组编号,结果将保存在返回的 groups字段中
const h = `
<h1>mkimq</h1>
<span>在线文档</span>
<h2>mkimq.com</h2>
`
const reg = /<?<tag>h[1-6]>(?<con>[\s\S]*)<\/\1>/gi
console.log(h.replace(reg, `<p>$<con></p>`))
组别名使用 ?<> 形式定义
重复匹配
基本使用
符号 | 说明 |
---|---|
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
禁止贪婪
正则表达式在进行重复匹配时,默认是贪婪匹配模式,也就是说会尽量匹配更多内容,但是有的时候我们并不希望他匹配更多内容,这时可以通过?进行修饰来禁止重复匹配
使用 | 说明 |
---|---|
*? | 重复任意次,但尽可能少重复 |
+? | 重复1次或更多次,但尽可能少重复 |
?? | 重复0次或1次,但尽可能少重复 |
{n,m}? | 重复n到m次,但尽可能少重复 |
{n,}? | 重复n次以上,但尽可能少重复 |
全局匹配
使用match 全局获取页面中标签内容,但并不会返回匹配细节
matchAll
在新浏览器中支持使用 matchAll 操作,并返回迭代对象
const str = 'mankeung'
const reg = /[a-z]/ig
for (const interator of str.matchAll(reg)) {
console.log(interator)
}
在原型定义 matchAll方法,用于在旧浏览器中工作,不需要添加g 模式运行
String.prototype.matchAll = function(reg) {
const res = this.match(reg)
if (res) {
const str = this.replace(res[0], '^'.repeat(res[0].length))
const match = str.matchAll(reg) || []
return [res, ...match]
}
}
const str = 'mankeung'
console.dir(str.matchAll(/(U)/i))
exec
使用g模式修正符并结合exec循环操作可以获取结果和匹配细节
function search(string, reg) {
const matchs = []
let data = ''
while ((data = reg.exec(string))) {
matchs.push(data)
}
return matchs
}
const str = `
<h1>mkimq.com</h1>
<h2>mankeung</h2>
<h1>在线文档</h1>
`
console.log(search(str, /<(h[1-6])>[\s\S]+?<\/\1>/gi))
字符方法
String提供的支持正则表达式的方法
search
search() 方法用于检索字符串中指定的子字符串,也可以使用正则表达式搜索,返回值为索引位置
const str = 'mkimq.com'
console.log(str.serach('com'))
console.log(str.search(/\.com/i))
match
const str = 'mkimq.com'
console.log(str.match('com'))
console.log(str.match(/\.com/i))
matchAll
const str = 'mkimq'
const reg = /[a-z]/ig
for (const iterator of str.matchAll(reg)) {
console.log(iterator)
}
split
const str = '2022-03-18'
console.log(str.split('-'))
console.log(str.split(/-|\//))
replace
replace方法不仅可以执行基本字符替换,也可以进行正则替换
const str = '2022/03/18'
console.log(str.replace(/\//g, '-'))
变量 | 说明 |
---|---|
$$ | 插入一个 "$"。 |
$& | 插入匹配的子串。 |
`$`` | 插入当前匹配的子串左边的内容。 |
$' | 插入当前匹配的子串右边的内容。 |
$n | 假如第一个参数是 RegExp 对象,并且 n 是个小于100的非负整数,那么插入第 n 个括号匹配的字符串。提示:索引是从1开始 |
const str = '=mkimq='
console.log(str.replace(/mkimq/g, "$`$&$'"))
const phone = '(010)9999999 (020)88888888'
console.log(phone.replace(/\((\d{3,4})\)(\d{7,8})/g, '$1-$2'))
const txt = '在线文档,记录每一步。'
console.log(txt.replace(/文档/g, `<a href="https://www.mkimq.com">$&</a>`))
为链接添加上https ,并补全 www.
<body>
<main>
<a style="color:red" href="http://www.mkimq.com">
mkimq
</a>
<a id="l1" href="http://baidu.com">百度</a>
<a href="http://yahoo.com">雅虎</a>
<h4>http://www.mkimq.com</h4>
</main>
</body>
<script>
const main = document.querySelector("body main");
const reg = /(<a.*href=['"])(http)(:\/\/)(www\.)?(mankeung|mkimq)/gi;
main.innerHTML = main.innerHTML.replace(reg, (v, ...args) => {
args[1] += "s";
args[3] = args[3] || "www.";
return args.splice(0, 5).join("");
});
</script>
回调函数
replace 支持回调函数操作,用于处理复杂的替换逻辑
变量名 | 代表的值 |
---|---|
match | 匹配的子串。(对应于上述的$&。) |
p1,p2, ... | 假如replace()方法的第一个参数是一个 RegExp 对象,则代表第n个括号匹配的字符串。(对应于上述的$1,$2等。)例如,如果是用 /(\a+)(\b+)/ 这个来匹配,p1 就是匹配的 \a+,p2 就是匹配的 \b+。 |
offset | 匹配到的子字符串在原字符串中的偏移量。(比如,如果原字符串是 'abcd',匹配到的子字符串是 'bc',那么这个参数将会是 1) |
string | 被匹配的原字符串。 |
NamedCaptureGroup | 命名捕获组匹配的对象 |
正则方法
test
检测是否匹配
console.log(/^\w+@\w+\.\w+$/.test('mankeung1011@gmail.com'))
exec
不使用g修饰符时与match方法使用相似,使用g修饰符后可以循环调用直到全部匹配完。
- 使用g修饰符多次操作时使用同一个正则,即把正则定义为变量使用
- 使用g修饰符最后匹配不到时返回 null
断言匹配
断言虽然写在扩号中但它不是组,所以不会在匹配结果中保存,可以将断言理解为正则中的条件。
(?=exp)
零宽先行断言 ?=exp 匹配后面为 exp 的内容
把后面是教程的mkimq汉字加上链接
const str = 'mkimq在线文档,mkimq教程。'
const reg = /mkimq(?=教程)/gi
console.log(str.replace(reg, v => `<a href="https://www.mkimq.com">${v}</a>`))
// const reg = /^(?=[a-z]{5}$)/i // 必须为五位
// 价格后面 添加上 .00
// const reg = /(\d+)(.00)?(?=元)/gi
// str.replace(reg, (v, ...args) => {
// args[1] = args[1] || '.00'
// return args.splice(0, 2).join('')
// })
(?<=exp)
零宽后行断言 ?<=exp 匹配前面为 exp 的内容
// 匹配前面是 mkimq
const reg = /(?<=mkimq)\d+/i
// 匹配前后都是数字的内容
const reg = /(?<=\d)[a-z]+(?=\d{3})/i
(?!exp)
零宽负向先行断言 后面不能出现 exp 指定的内容
(?<!exp)
零宽负向后行断言 前面不能出现exp指定的内容