我自己是一名从事了多年开发的web前端老程序员,目前辞职在做自己的web前端私人定制课程,今年年初我花了一个月整理了一份最适合2019年学习的web前端学习干货,各种框架都有整理,送给每一位前端小伙伴,想要获取的可以关注我的头条号并在后台私信我:前端,即可免费获取。
当想匹配比如手机号、邮箱等特殊形式的字符串时,大家往往都会想到正则表达式,不过当脑海中浮现出那一串神奇的既没有注释,甚至没有空格的字符串时,内心又会充满恐惧。此时你可能会动用搜索引擎,搜到别人写的那一串神奇字符,拷贝到自己的代码中,稍经测试,发现竟然能用,不禁发出一阵牛逼,然后就去写别的代码逻辑去了。再也不想看这一串字符。
是不是你也有过上述经历呢,对正则表达式怀有很大的畏惧之情,觉得自己无法驾驭它,但有时候又实在离不开它。下面我会简单介绍下正则表达式的由来,和驾驭它的正确姿势。本篇会议 JS 端的正则表达式为例子来演示。
由来
正则表达式诞生于 1951 年,它起源于对形式语言的数学研究。1968 年开始,正则表达式广泛应用于编辑器的字符匹配,和编译器中的词法分析。Ken Tompson 实现了第一个切实可用的匹配器,后来被用在了 Unix 的 grep 搜索工具中,g/re/p 全称 Global search for Regular Expression and Print matching lines。真正诞生更现代化的正则表达式实现是在 19 世纪 80 年代的 Perl 语言中。现在正则表达式已被各种语言广泛支持,比如 Java、Python、JavaScript 等。
由于正则表达式趋向于极致的简洁,甚至不惜容忍含义模糊,而且它不支持注释和空白,所有部分都紧密排列在一起,导致我们难以理解也属正常,但尽管有这些缺点,正则表达式依然被广泛的应用着。
举个例子
我们要提取一个 http 链接的 scheme 和 domain,那正则表达式如何写呢?我们可以简化成如下
let parse_url = /^(https?):\/{0,3}([0-9.\-A-Za-z] )(?::\d )?.*$/ parse_url.exec('http://www.kujiale.com:80/college') 会得到一个数组,["http://www.kujiale.com:80/college", "http", "www.kujiale.com"] 数组的首位是整个输入,第二位和第三位则为我们想要的内容。
下面我们一步步来分析该正则表达式
^ $ 表示匹配开始和结尾,它是一个锚,只有开头和结尾处满足该正则的字符串才匹配,而如果没有该锚,意味着,如果字符串中间部分满足该正则也会被匹配到。
比如当我们不用 ^ 符号时,同样执行上述代码,如果此时来匹配 xhttp://www.kujiale.com:80/college 则一样会成功匹配,而使用了 ^ 则会匹配失败。
注:^ 既可以表示开始的锚,也可以用作语意非。后面会详述。
分组
上面例子中得到的结果为数组,包含了三部分内容,而无其他。在正则中,如果我们想获取其中的部分内容,我们就会使用一对括号来保住所要提取的部分,我们把这部分称为一个分组 (...), 一个分组会复制它所匹配的文本,并将其放到 result 数组里,每个分组会被指定一个编号,1,2,3... 。如果分组有嵌套,则会从按从左到右,从外到里的方式输出。如:
/((ht)(tp))(s)/.exec('https') 输出:["https", "http", "ht", "tp", "s"]
当我们不想捕获某个分组时,我们需要在分组中加上 ?: 表示不要捕获该分组,就像最初的例子中不要捕获端口号那样使用。
/((ht)(?:tp))(s)/.exec('https')
输出:["https", "http", "ht", "s"]
分组被捕获后,我们可以使用 \ 编号来使用该分组。\1 表示分组一,\2 表示分组二,以此类推。比如我们要找一个文本中搜索一对重复的单词
const reg = /([A-Za-z] )\s \1/
reg.test('aaa bbb') false
reg.test('aaa aaa') true
字符集
正则匹配最终匹配的还是字符,所以我们需要表达字符的方式。如果只是匹配一个字符,那很简单,比如
/a/.test('a') true
我们也可以加上 或 逻辑 |
/a|b/.test('b') true
/a|b/.test('a') true
如果是一类字符,如果一直用 或 逻辑那也太繁琐了,此时我们可以使用 [] ,比如匹配所有 a 到 z 的字符
/[a-z]/.test('b') true
/[ab]/.test('b') true
如果想加上 非 的语义,就像我们上面所说,那我们就需要在字符集中加上 ^ 符号
/[^ab]/.test('b') false
由于某些字符集经常用到,所以正则帮我们提取了出了这些常用的字符集,用一些特殊符号来表示
. 匹配除 \n \r 之外的任何单个字符
\d 等价于 [0-9]
\D 等价于 [^0-9]
\w 等价于[0-9A-Z_a-z]
\W 等价于[^0-9A-Z_a-z]
\s 匹配任何不可见字符, 比如空格换行等
\S 匹配任何可见字符
注:当字符集中使用到一些保留字符时,需要使用 \ 进行转意
匹配次数
我们除了匹配字符,还得需要定义匹配字符的数量,比如我们要匹配两个字母的单词,不是写成/\w\w/ 而是可以写成 /\w{2}/
字符或字符集后面跟 {n, m} 表示该字符或字符集可重复的次数范围,一样,正则为我们封装了一些常用的次数匹配规则,比如 , *, ? ,如下表所示。
{n,} 大于等于 n 次
{n} 等于 n 次
等价于 {1,}
* 等价于 {0,}
? 等价于 {0,1}
标识
除了上述说的这些,你还可能看到过有些正则表达式后面加了 /g 或 /i 或 /m。 比较常用的是 /g,/i 表示忽略字符大小写,/m 表示多行匹配,都不怎么常用。
其中 /g 的使用也有限制,比如 test、search、和 RegExp 的 exec 方法会忽略掉 /g 标识。/g可以用在 string.match 和 string.replace 方法中, 如下例子:
"[22].[44].[33].".match(/\d /g) // 输出 ["22", "44", "33"]
"[22].[44].[33].".match(/\d /) // 输出 ["22"]
"aaaa".replace(/a/, 'b') // 输出 "baaa"
"aaaa".replace(/a/g, 'b') // 输出 "bbbb"
写在最后
我们再回过头来看下这个正则,是不是也觉得不难了。
/^(https?):\/{0,3}([0-9.\-A-Za-z] )(?::\d )?.*$/
(https?) 表示捕获该部分,s 可有可无。/{0, 3},表示匹配 /,0 到 3 个都满足。后面就是匹配 域名、端口和后面的 path、query 部分。另外以上例子部分使用 javascript 语言,java 的正则表达式和 js 本质没有区别,但要特别注意 Java 中的转译需要用两个斜杆 \\ 。
来源:https://www.jianshu.com/p/cf774c79a869
作者:CPPAlien