前言
在前端领域中,正则表达式是一种实用且强大的工具,可以帮助我们快速地匹配特定的内容,完成特定的需求,有着事半功倍的效果。比如,在用户信息模块中,我们会使用正则表达式去匹配手机号格式是否正确、身份证号是否正确,以及用户姓名是否输入规范等,在这些场景中使用正则表达式就显得很简单且高效。
最近刚好看到一个npm包,名字叫markdown-title,这个包在npm上的周下载量就达到了3k。其功能很简单也很实用,也就是从给点的Markdown文本中读取一级标题的内容并返回。然而,细看之后其代码实现其实很简单,只有短短的8行代码!而这正是借助正则表达式实现的。
借此机会,我们学习和回顾下正则表达式的用法。
markdown-title
npm包的代码内容如下所示。
我们可以看到,文件导出了一个 markdownTitle 函数,从输入的 markdown 内容中读出一级标题(以单个#开头)并返回,如果没有匹配到则返回的是undefined。
const markdownTitle = markdown => {
let matches = markdown.match(/^#[^#][\s]*(.+?)#*?$/m)
if (matches && matches.length) {
return matches.pop().trim()
}
return
}
module.exports = markdownTitle
这个npm包使用起来也很简单:
const markdownTitle = require('markdown-title')
let markdown = `
# Title
`
let title = markdownTitle(markdown) //=> Title
// # Title
// # I can title? #
我们输入markdown字符串后,最终会返回获取到的一级标题。上面两种标题都能正确识别到。
分析
markdown.match(/^#[^#][\s]*(.+?)#*?$/m)
我们可以看到,npm包的核心代码是通过正则表达式实现的,这句代码是什么意思呢?
首先,match() 是js字符串提供的方法,该方法用于在给定的js字符串中查找与正则表达式匹配的内容。如果能找到匹配,则返回一个数组,第0项是完全匹配的整个字符串,后续项是捕获组匹配的内容,还包含 index (匹配开始位置) 和 input (原始字符串) 属性。如果没找到匹配,则返回 null。
其次,正则表达式部分,常见的语法包括:
^#表示 匹配行首的 # 符号$表示到达行尾m是多行模式的标志
再次,其他部分是重点,包括:
-
[^#]- 确保下一个字符不是#(避免匹配二级标题##)。在正则表达式中,[ ] 表示一个字符集合,而 ^ 在 [ ] 的开头时表示"否定"或"排除"。 -
[\s]*- 匹配0或多个空白字符。#[^#][\s]*组合起来表示:匹配一个 # 号,后面跟着一个非 # 的字符,然后可以有任意数量的空白字符。例如以下几种情况都是匹配的:#标题(无空格) # 标题(一个空格) # 标题(多个空格) -
(.+?)- 非贪婪匹配标题内容(捕获组)。其中,() 表示捕获组,会把匹配的内容保存起来。
.+ 表示匹配任意单个字符一次或多次。
? 在这里表示"非贪婪"模式。正则表达式默认是贪婪模式,+ 会尽可能多地匹配字符。例如,
a.*b匹配 "aabab" 时会匹配整个字符串。+? 表示匹配尽可能少但至少一个字符,例如
a.*?b匹配 "aabab" 时只会匹配 "aab"。 -
#*?$- 匹配行尾可能存在的 # 号。组合起来#*?$表示:在行尾可能有零个或多个井号(#),以最少必要为原则匹配。举例说明:
匹配 # 标题(无末尾#号)
匹配 # 标题#(一个末尾#号)
匹配 # 标题###(多个末尾#号)
但不匹配 # 标题# 其他内容(因为$要求必须是行尾)
最后,返回值是matches.pop().trim(),也就是返回匹配到的内容,并去掉首尾空格。
到这里,代码也就分析完了。我们不难看到,作者借助正则表达式去获取markdown文本的一级标题(获取第一个匹配到的一级标题,并排除了二级标题等),还兼容了# 标题###等情形。
后记
简而言之,正则表达式是一个强大的工具,也是前端乃至后端必备的一种技能。
多从别人的代码中去思考和学习,能学到更多东西。
开卷有益!