先记住这个答案
默认情况下,正则里的 . 能匹配几乎所有字符,唯独不匹配换行符,所以 /a.b/ 匹配不了 'a\nb'。s 标志(dotAll)就是把换行符也纳入点号的范围,/a.b/s 就能匹配了。m 标志(multiline)解决的是另一件事:默认 ^ 只匹配整个字符串的开头,$ 只匹配结尾,开了 m 之后,它们还会匹配每一行的开头和结尾。一个管点号能不能跨行,一个管锚点按不按行,两者互相独立,可以一起用。
s管点号:让.能匹配换行m管锚点:让^和$按行匹配- 两个标志互相独立,可以一起用
- 不开
s时用[\s\S]匹配任意字符
先看两个标志各自改变了什么
同一段含换行的文本,分别用四个正则去测。
const text = 'a\nb'
console.log(/^a.b$/.test(text)) // 点号不匹配换行
console.log(/^a.b$/s.test(text)) // 开 s,点号吃掉了换行
console.log(/^b$/.test(text)) // ^ 只认整个字符串的开头
console.log(/^b$/m.test(text)) // 开 m,第二行的开头也算查看输出与解释
false
true
false
true第二行只有开了 s,点号才能跨过 \n。第四行只有开了 m,正则 ^b$ 才能把第二行当成独立的一行。反过来试也一样:/^a.b$/m 还是 false,因为 m 不管点号;/^b$/s 也还是 false,因为 s 不管锚点。
点号默认排除的不只是 \n,还有 \r 以及 Unicode 的行分隔符 \u2028 和段分隔符 \u2029。Windows 的换行是 \r\n,不开 s 时 . 两个都匹配不了。
实际用法:处理多行日志
处理日志、Markdown、配置文件时,这两个标志最常用。比如从日志里提取一整条错误,错误信息后面跟着多行调用栈。
s 跨行提取,用 m 按行定位JavaScriptconst log = '[ERROR] 请求失败\n at fetchUser\n at main\n[INFO] 重试成功'
console.log(log.match(/\[ERROR\].*/)[0]) // 不开 s,遇到换行就停
// 开 s,懒惰匹配一直吃到下一条日志之前
const block = log.match(/\[ERROR\].*?(?=\n\[)/s)[0]
console.log(block.split('\n').length) // 错误信息加两行调用栈
console.log(log.match(/^\[\w+\]/gm).join(' ')) // 开 m,每行行首都能匹配查看输出与解释
[ERROR] 请求失败
3
[ERROR] [INFO]第一个不开 s,只拿到了第一行,调用栈全丢了。第二个开了 s,于是 .*? 可以跨行,配合前瞻 (?=\n\[) 在下一条日志开头之前停下,拿到了完整的三行。第三个用 m 让 ^ 匹配每一行的开头,把每条日志的级别都找出来了。
- 没有
s的年代怎么写 s标志是 ES2018 才加的。以前要匹配包括换行在内的任意字符,常见写法是[\s\S],意思是「空白或非空白」,合起来就是所有字符,也有人写[^]。老代码里看到这两种写法,意思和开了s的点号一样。现在直接用s更好读。
容易答错的地方
- 以为
m能让点号跨多行 - multiline 这个名字很容易让人以为它和「多行匹配」有关,但它只改变
^和$的含义。点号能不能匹配换行,只由s决定。想跨行匹配内容却只开了m,结果不会有任何变化。 - 开了
s就用贪婪匹配吞整段 /<div>.*<\/div>/s会从第一个<div>一直匹配到最后一个</div>,中间所有内容都被吞进去。跨行匹配时通常要用懒惰量词.*?,或者用前瞻限定结束位置。解析 HTML 这类嵌套结构,正则本来就不合适,应该用DOMParser。
面试官还会怎么问?
s 和 m 可以同时用吗?
可以,写成 /^BEGIN.*?END$/sm。s 让中间的 .*? 能跨行,m 让 ^ 和 $ 匹配 BEGIN 所在行的开头和 END 所在行的结尾。适合从大段文本里提取「某行开始、某行结束」的块,比如提取 PEM 证书内容。
怎么判断一个正则开了哪些标志?
读它的属性。re.dotAll 对应 s,而 re.multiline 对应 m,还有 re.global 对应 g。re.flags 会返回所有标志组成的字符串,比如 'gms'。
m 模式下 $ 匹配在换行符前面还是后面?
前面。$ 匹配的是换行符之前的那个位置,^ 匹配的是换行符之后的那个位置,它们都是零宽的,不会吃掉换行符本身。所以 /^.*$/gm 拿到的每一行都不带 \n。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。