欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JavaScript的正则也有单行模式了

程序员文章站 2022-03-10 21:38:26
正则表达式最早是由 ken thompson 于 1970 年在他改进过的 qed 编辑器里实现的,正则里最简单的元字符 “.” 在当时所匹配的就是除换行符外的任意字符:...

正则表达式最早是由 ken thompson 于 1970 年在他改进过的 qed 编辑器里实现的,正则里最简单的元字符 “.” 在当时所匹配的就是除换行符外的任意字符:

"." is a regular expression which matches any character except <nl>.

上面这句话出自 qed 在 1970 年的官方文档,这可能是史上第一份正则文档。

为什么要这么规定?是因为 qed 是以行为单位来编辑文件的,而且行尾的换行符也算在这一行的内容里。比如你想把一段代码中所有的单行注释删掉,在 qed 里可以用下面这句命令: 

1,$s#//.*##

如果 “.” 能匹配到换行符,那么换行符也会被删除,会导致这些行和它的下一行合并,这通常都不是我们想要的结果,所以,“.” 在最初发明时被设计成了不能匹配换行符。虽然现在的操作系统上已经没有 qed 命令让我们测试了,但我们还有 vim,vim 里的 “.” 也一样不能匹配换行符,因为同样的原因。

不像在 node 中,读取文件通常是一股脑读完整个文件,perl 继承了众多 linux 命令按行读取文件的传统,像这样:

while (<>) {print $_}

_ 的末尾也有换行符,所以 perl 也就很自然的继承了 qed 的 “.” 不匹配换行符的规定。但 perl 毕竟是门编程语言,而不是编辑器,它的正则要匹配的对象不单单会是单行文本,还可能是多行文本,因此在它的正则中,“.” 有跨行匹配的需求,因此 perl 发明了正则的单行模式 /s,即让 “.” 也能匹配换行符。

perl 中用来打开单行模式的 /s 修饰符的官方描述是 “treat the string as single line”,这个 “single line” 要这么理解:“.” 在普通模式下只能匹配行内字符,不能跨行;而在单行模式下,perl 会假装把多行字符串看成一行,把其中的换行符看做是行内字符,所以 “.” 也就能匹配它们了。更形象点说,就是把下面的三行文本

1
2
3

看成 "1\n2\n3\n" 一行文本,单行模式就是这个意思。

但要命的是,因为同样的原因(字符串变量可以包含多行文本),perl 还发明了 /m 修饰符,即多行模式,官方描述是 “treat the string as multiple lines”,这个模式 javascript 的正则里自古也有,这里这个“多行”的意思是说:^ 和 $ 元字符默认不会匹配一个字符串中间的那些换行符前后的位置,即认为字符串永远只有一行,打开多行模式后就能匹配了。

也就是说,单行模式和多行模式是针对不同的元字符的,刚接触正则的人都会被“单行模式”和“多行模式”这两个看似是相对应的概念,实则毫无关联的名词给搞晕。

后来,ruby 的作者可能觉得“单行模式”这个正则术语起的不好,特例独行把让 “.” 匹配换行符这一模式称之为“多行模式”,即让 .* 之类的正则能够匹配多行了,所以也完全讲得通,修饰符也用了 /m(ruby 中默认会开启 perl 中的“多行模式”,所以 /m 没被占用),这真是雪上加霜,更乱了。 

再后来,python 作者可能也觉得应该避免“单行模式”这个叫法,于是起了个新的名字 “dotall”,也就是让 dot 能匹配所有字符的意思,很好的名字,再后来 java 也使用了这个名字。

上面回顾了一下历史,解释了下单行模式的由来以及说明了下单行模式这个名字起得不好。v8 最近刚刚实现了一个 stage 3 的 es 提案 ,这个提案为 javascript 的正则引入了 /s 修饰符和 dotall 属性,dotall 属性是学了 python 和 java,/s 修饰符是继承了 perl 的,这里也没必要发明一个新的修饰符比如 /d,只会让事情更复杂。/s 在 javascript 的具体效果是让 “.” 能匹配以前不能匹配的四个:\n(换行)、\r(回车)、\u2028(行分隔符)、\u2029(段落分隔符):

/foo/s.dotall // true
/^.{4}$/s.test("\n\r\u2028\u2029") // true

其实就是个很简单的东西,但可能一些没有接触过 javascript 以外的正则的同学到时候学到这个新的模式后会产生困惑,这里再澄清一下:多行模式控制的是 ^ 和 $ 的表现,单行模式控制的是 “.” 的表现,两者没有直接关系。

然而当初引入单行模式和多行模式这两个易混淆概念的 perl 语言,已经在 perl 6 中完全删除了这两个模式:“.” 号默认就匹配换行符,\n 可以匹配换行符除外的任意字符;^ 和 $ 始终匹配字符串的首尾,而新引入了 ^^ 和 $$ 两个元字符来匹配行的首尾。

过去我们常用的单行模式的替代品 [^] 或者 [\s\s] 也不是完全没有用了,比如在一些使用 javascript 正则的编辑器里(vs code、atom),不太可能给你提供开启单行模式的界面。不过说起编辑器里的正则功能,用 javascript 实现的编辑器的正则功能还是太弱了,比如不能在正则自身内部开启某些模式,比如要是在 sublime(使用 python 正则)里的话,在正则内部使用 (?s) 就能开启 dotall 模式,比如可以用 (?s)/\*.+?\*/ 匹配到所有的多行注释。