正则表达式,这个工具我们再熟悉不过了。工作中很多场景会用到它。常用它来做字符串匹配、查询、替换等等。特点是高效、简洁。
这篇文章主要介绍正则表达式中比较“高级”的用法,所谓高级不是真的高级,只是有些用法不太常见,阅读代码时容易造成困扰。不太常见所以看官不用死记硬背,阅读完留个印象即可,下次碰见了不懂再查呗。
默认要求读者已经掌握了正则表达式的基本用法。
取非操作
[] 中括号是正则表达式中常用来表示字符集合区间的工具,例如[0-9a-zA-Z] 可以匹配到任意一个数字或大小写字母字符。
但有时候需求不是要【白名单】而是要【黑名单】,例如查出所有非数字开头的单词。
文本
It was 19 April, 2019. The small, ageing oil tanker and
its crew of 15 had spent two days sailing south from the
port of Lagos to the Niger Delta, where oil was
discovered in the 1950s by Dutch.
正则表达式
/\b[^\s\d]\w*\b/ig
匹配结果
分析
这里用到了 \b 的限定符,这个符号表达的意思是单词边界,匹配一个单词的开始或结尾位置,注意匹配的是位置,而不是一个可见的字符。
重点是[^\s\d],这个表达式匹配的是非空字符及非数字字符。^符号在这里起到的作用就是给集合内的字符区间取反。这个取反效果仅当它是[]中的第一个字符出现时才有效。
如果改成[\d^],匹配的是数字或 ^符号。
当 ^ 脱离 [] 时表达的又是另一个意思,匹配的是字符串开头的位置(注意同样是位置,而不是字符)。
懒惰和贪婪模式
正则表达式中存在贪婪模式和懒惰模式的区分,常见的 *(零或多个),+(一个或多个)就是贪婪模式匹配符。贪婪模式在匹配时的表现是多多益善。
例如下面的例子,匹配多个 <B> 标签。
文本
This offer is not available to customers living
in <B>AK</B> and <B>HI</B>.
正则表达式
/<B>.*<\/B>/ig
匹配结果
由于使用了 * 符,正则表达式在匹配文本时将尽可能多地匹配文本,以至于多个<B>标签被识别为一个,因为它贪婪啊。
懒惰模式
/<B>.*?<\/B>/ig
懒惰模式则相反,匹配时表现为点到为止,见好久收。切换懒惰模式只需要在 * 后面加上 ? 符号即可。
目标文本被正确识别为两个标签。
回溯引用
回溯引用是比较容易让新人摸不着头脑的。为了理解回溯引用,我们还拿 HTML 标签来举例子。
HTML 中除了像 <img />、<meta /> 之类的标签外,大部分标签是以<开放标签>内容</闭合标签>的形式存在,开放标签和闭合标签必须匹配才是正确的。
要求写一个正则表达式,将正确的标题标签(h1~6)及内容匹配出来。
<body>
<h2>Hello</h2>
<h3>Regexp</h4>
</body>
❌错误示范
标签总共就 h1~6 六种,标签用文本h[1-6]匹配,内容则用.(任意字符) + *(零或多个) + ?(懒惰模式)进行匹配,很容易就写出以下这样的正则表达式。
/<h[1-6]>.*?<\/h[1-6]>/ig
问题是,这个表达式匹配的条件是,开放标签是 h16,闭合标签也是 h16。这样宽松的条件会把 <h3>Regexp</h4> 也认为是合理的标签。而需求是开放标签和闭合标签应该严格匹配。
✅正确示范
/<(h[1-6])>.*?<\/\1>/ig
改动点是,我们把开放标签用(),包裹起来,形成子表达式,在闭合标签中引用这个子表达式\1,\1 表示引用第一个子表达式匹配内容,以此类推\2就匹配第二个子表达式(不过这个正则里面仅有一个子表达式)。
回溯引用在 Javascript 中也常会用到。例如字符串的 replace 方法,不过换成了 $1,$2... 这种形式出现。
例如实现一个简单版本的模板替换方法:
function compile (template) {
return function render (params) {
return template.replace(/\$\{(\w+)\}/g, function ($0, $1) {
return params[$1] || undefined;
})
}
}
const render = compile('${a}-${b}')
render({
a: 1,
b: 2
});
// 运行结果:1-2
\$\{(\w+)\ 可以匹配上指定格式的文本,\w+将匹配一个或多个数字,字母或下划线。通过子表达式将变量名匹配出来,结合回溯引用,就能轻松实现模板值替换了。
前后查找
在使用正则表达式时,我们常会借助一些标记来帮助匹配文本的位置,而标记位又往往不是目标文本,只是起协助定位的作用。
例如要替换 HTML 文本中的 title 内容,可以借助<title>标签来定位,但是最终它并不需要被替换。
文本
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
可以使用子表达式+回溯引用的方式来进行替换。
正则
(<title>)(.+)(<\/title>)
也可以试试前后查找的功能
/(?<=<title>).+(?=<\/title>)/ig
匹配结果
前后查找的方式显得整洁很多,直接替换目标文本,不用去处理回溯引用带来的负担。
分析
这里就用到正向后查找 ?<=(不是浏览器都支持) 和正向前查找 ?=,其他查找方式还有负向前查找?!,负向后查找?<!等。这类匹配符的特点是仅用于匹配,但是不消费。
结尾
这篇文章列举了一些很实用,但是新手不好理解的一些正则表达式使用技巧.水平有限,只能帮助你快速了解,完全掌握需要更深入地学习。