工具-能写会看正则表达式

语言: CN / TW / HK

theme: nico

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第25天,点击查看活动详情

正则表达式,很常用的一个技能点,但是一般的开发流程中都是这样的: 1. 需要验证数据 2. 上网搜一下正则表达式 3. CV 搞定!!!

今天有时间回看了一下文档,简单整理了一下里面需要注意的点,并且通过分析几个常见的正则表达式,下次遇到正则争取不再只依靠 CV 大法!

基础部分

基本语法

/正则表达式主体/修饰符(可选)

解释一下就是,正则表达式是由 两个 / 包裹的 正则表达式主体 和紧跟在后面的修饰符组成的。所以,也只有两个斜线是固定格式,里面的所有内容都属于 正则表达式主体。

为什么这啰嗦呢?

因为我曾经有一段时间以为正则表达式的语法是 /^正则表达式主体$/修饰符,即 以 /^ 开始,$/ 结束。其实并不是,是因为 ^ 的含义是 以 ^后面的内容开始,而 $的含义是 以 $之前的结束,它俩有个好听的名字,叫量词。下面会单独记录一下。

修饰符

修饰符只有三个,而且都很实用:

  • i : 忽略大小写
  • g : 匹配所有满足正则表达式的
  • m : 执行多行匹配

但是m用的比较少,前面两个是很常见的!

方括号

| 表达式 | 描述 | | -- | -- | | [abc] | 查找方括号之间的任何字符。 | | [^abc] | 查找任何不在方括号之间的字符。 | | [0-9] | 查找任何从 0 至 9 的数字。 | | [a-z] | 查找任何从小写 a 到小写 z 的字符。 | | [A-Z] | 查找任何从大写 A 到大写 Z 的字符。 | | [A-z] | 查找任何从大写 A 到小写 z 的字符。 | | [adgk] | 查找给定集合内的任何字符。 | | [^adgk] | 查找给定集合外的任何字符。 | | (red\|blue\|green) | 查找任何指定的选项。|

方括号是实现正则表达式能够灵活匹配任何场景的核心元素,他代表的就是一个集合,可以满足模糊匹配的效果。

元字符

| 元字符 | 描述 | | -- | -- | | . | 查找单个字符,除了换行和行结束符。 | | \w | 查找数字、字母及下划线。 | | \W | 查找非单词字符。 | | \d | 查找数字。 | | \D | 查找非数字字符。 | | \s | 查找空白字符。 | | \S | 查找非空白字符。 | | \b | 匹配单词边界。 | | \B | 匹配非单词边界。 | | \0 | 查找 NULL 字符。 | | \n | 查找换行符。 | | \f | 查找换页符。 | | \r | 查找回车符。 | | \t | 查找制表符。 | | \v | 查找垂直制表符。 | | \xxx | 查找以八进制数 xxx 规定的字符。 | | \xdd | 查找以十六进制数 dd 规定的字符。 | | \uxxxx | 查找以十六进制数 xxxx 规定的 Unicode 字符。 |

元字符是对一些常用字符进行了归类合并,可以代指某一类的匹配目标值。配合方括号和语法来实现灵活匹配的效果。

量词

| 量词 | 描述 | | -- | -- | | n+ | 匹配任何包含至少一个 n 的字符串。例如,/a+/ 匹配 "candy" 中的 "a","caaaaaaandy" 中所有的 "a"。 | | n | 匹配任何包含零个或多个 n 的字符串。例如,/bo/ 匹配 "A ghost booooed" 中的 "boooo","A bird warbled" 中的 "b",但是不匹配 "A goat grunted"。 | | n? | 匹配任何包含零个或一个 n 的字符串。例如,/e?le?/ 匹配 "angel" 中的 "el","angle" 中的 "le"。| | n{X} | 匹配包含 X 个 n 的序列的字符串。例如,/a{2}/ 不匹配 "candy," 中的 "a",但是匹配 "caandy," 中的两个 "a",且匹配 "caaandy." 中的前两个 "a"。 | | n{X,}| X 是一个正整数。前面的模式 n 连续出现至少 X 次时匹配。例如,/a{2,}/ 不匹配 "candy" 中的 "a",但是匹配 "caandy" 和 "caaaaaaandy." 中所有的 "a"。 | | n{X,Y} | X 和 Y 为正整数。前面的模式 n 连续出现至少 X 次,至多 Y 次时匹配。例如,/a{1,3}/ 不匹配 "cndy",匹配 "candy," 中的 "a","caandy," 中的两个 "a",匹配 "caaaaaaandy" 中的前面三个 "a"。注意,当匹配 "caaaaaaandy" 时,即使原始字符串拥有更多的 "a",匹配项也是 "aaa"。 | | n$ | 匹配任何结尾为 n 的字符串。 | | ^n | 匹配任何开头为 n 的字符串。 | | ?=n | 匹配任何其后紧接指定字符串 n 的字符串。 | | ?!n | 匹配任何其后没有紧接指定字符串 n 的字符串。 |

量词也是标识,如上面表格中所示,n 是指由上面的 方括号和元字符组成的,n 之外的内容是量词。

如开篇所说,我一开始以为正则表达式的语法是 /^$/ 结束,看到这里以后才确定, ^ 和 $ 是量词,各有其含义,并不是语法中必要的。

灵活使用量词能够对于要求比较高的正则精准匹配。

常见表达式分析

这个文章的目的不是为了搜集各种各样的正则表达式而写的,而是为了能够读懂正则表达式,从而方便对现有的正则进行改造或者判断正则是不是有问题。

所以只会拿几个比较有特色的正则表达式进行简单的分析,因为这些正则是从其他地方借鉴过来的,可能有问题,但是不妨碍我们学习,如果真的看出问题来了,才说明真的有了进步了!

用户名验证

这个正则验证的是用户名,要求只能出现 数字大小写字母-_ 这几样元素,长度在 4 到 16 位之间。

是一个很常见、很实用的场景,不只是为了验证用户名,很多复杂的验证都是这个模板,即从第一位开始到最后一位,每一位都要满足一定的条件,而且长度有限制。

js /** * /^ 开始 * [] 查找方括号中间的任何字符 * a-z 小写字母 * A-Z 大写字母 * 0-9 数字 0-9 * _ 下划线 * {4,16} 长度 * $/ 结束 */ const userPattern = /^[a-zA-Z0-9_-]{4,16}$/; const userStr1 = "Admin"; console.log("userStr1.test.userPattern :", userPattern.test(userStr1));

如上所示,我们可以通过修改方括号里面的内容,就可以使正则表达式支持其他字符,通过修改长度就可以满足其他长度的校验。

Email 和 手机号 验证

```js /* * Email 正则表达式 * 分为三部分 * 第一部分:对每个字符进行校验,只能是字母+数字和_-.的组合 * 第二部分:以 @ 开头且其他部分和第一部分一致 * 第三部分:以 . 开头然后由大小写字母组成长度为 2-4 的字符串结尾 / const ePattern = /^[A-Za-z0-9_-.]+\@[A-Za-z0-9_-.]+.[A-Za-z]{2,4}$/; const email = "[email protected]"; console.log("email.test.ePattern :", ePattern.test(email));

// 手机号正则 var mPattern = /^[1][3458][0-9]{9}$/; ```

这个正则表达式就是在用户名的那个基础上进行了扩展,可以使正则表达式支持对一个固定模式的文本进行校验。就像这次个正则表达式就是对 [email protected] 模式的字符串进行一个校验。

需要格外关注的是,这里面的 + 和 {2,4} 都是量词,如果没有很细的去了解的话容易把 + 看做加号或者连字符,其实它的含义是 匹配任何包含至少一个 n 的字符串

而手机号的则需要关注的是 [3458] 代指的是第二位只能是 3/4/5/8 ,而且必须跟在 1 后面,然后后面跟9位数字。

身份证号和url验证

难度上升,这两个正则要比上面的复杂一点。

js // 身份证号(18位)正则 /* * 第一部分,前六位地区:[1-9]\d{5},第一位是 1-9中的一个,后面5位是数字。\d 元字符,代指数字 * 第二部分,中间八位生日:(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31) * 年:(18|19|([23]\d))\d{2} 这里貌似不对,应该是 (18|19|20)\d{2} 即 从1800-2099 年 * 月:((0[1-9])|(10|11|12)) 01-09,然后是 10/11/12 * 日:(([0-2][1-9])|10|20|30|31) 这个就包含了除 00 之外的 0-31 的数字了 * 第三部分,四位数字或者三维数字加X:\d{3}[0-9Xx] * 除了这三部分之外:^标识开始 $标识结束 */ var cP = /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/; 18位的身份证号大家都熟悉吧!前6位是家乡的代码,中间8位是出生日期,后面4位则是四位数字或者三维数字加字母X。

知道了这个规则,回过头看上面的正则表达式。不难发现 的部分是有问题的,这个正则表达式无疑是把年份校验在了 1800 - 3999 年之间,不太合理。

js // url /* * 第一部分:((https?|ftp|file)://)? 问号代表 0个或1个,最多一个。涉及到的为 https 中的s 和 整个第一部分 * 第二部分:([\da-z.-]+).([a-z.]{2,6}) 包含.组成的 xxx.xxx.xxx 结构的字符串,xxx作为一个块,第一块可以包含数字,后面的块只有小写字母,最多有 7 个块 * 第三部分:([/\w .-]*)*/? 路由地址,由 \w、-、.组成,* 和 ?都是量词 xxx/xxx/ 或 xxx/xxx 结构 */ var urlP= /^((https?|ftp|file)://)?([\da-z.-]+).([a-z.]{2,6})([/\w .-]*)*/?$/;

url 也是很常用的一个数据类型了,而且数据很灵活,除了前半部分之外其他部分没法确定。而这个正则表达式正是这个思路,首先是开头以 http|https|ftp|file 加紧跟着的 :// 组成的第一部分,然后是域名由至少为 xxx.xxx.xxx 组成的 域名作为第二部分,紧接着第三部分是域名层级。

但是这个正则表达是第三部分,感觉怪怪的,/? 的位置感觉有点不对劲。

悬念

下面这个留给有缘人分析一下,算是作业吧:

// 密码强度正则,最少6位,包括至少1个大写字母,1个小写字母,1个数字,1个特殊字符 var pPattern = /^.*(?=.{6,})(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@``#$%^&*? ]).*$/; console.log("=="+pPattern.test("iFat3#"));

配套方法

正则表达式只是一种语法规范,如上面我借鉴别人的正则表达式来分析一样,是可以拿来直接用的,但是如何用就涉及到一系列的 API 了。

API 主要分为两类,一类是 String 对象上的几个方法:replace/split/match/search,还有 RegExp 对象上的几个方法:test/exec。

下面逐一测试并说明:

String.replace

这个方法大家几乎每个项目都在用,作用是替换字符串中的内容。

语法

stringObject.replace(regexp/substr,replacement)

一些小技巧

```js // 替换所有,对 g 修饰符的使用 var str="Welcome to Microsoft! " str=str + "We are proud to announce that Microsoft has " str=str + "one of the largest Web Developers sites in the world."

document.write(str.replace(/Microsoft/g, "W3School"))

// 首字母转为大写 name = 'aaa bbb ccc'; uw=name.replace(/\b\w+\b/g, function(word){ return word.substring(0,1).toUpperCase()+word.substring(1);} );

// replacement 中的 $ 字符具有特定的含义 // $1、$2、...、$99 表示从左到右,正则子表达式(组)匹配到的文本

// 将把 "Doe, John" 转换为 "John Doe" 的形式 var name = "Doe, John"; name.replace(/(\w+)\s, \s(\w+)/, "$2 $1"); // John Doe

// 把所有的花引号替换为直引号 var name2 = '"a", "b"'; name2.replace(/"([^"]*)"/g, "'$1'"); // "'a', 'b'"

// $& 表示 regexp 相匹配的子串。 var str = 'Visit Microsoft'; str = str.replace(/Visit Microsoft/g,"$& ,W3School"); console.log(str); // Visit Microsoft ,W3School

// $位于匹配子串左侧的文本。 var str = 'Visit Microsoft'; str = str.replace(/Microsoft/g,'$'); console.log(str); // Visit Visit

// $' 位于匹配子串右侧的文本。 var str = 'Visit Microsoft'; str = str.replace(/Visit/g,"$'"); console.log(str); // Microsoft Microsoft

// $$ 直接量符号 插入一个"$" var str = "javascript"; str = str.replace(/java/,"$$") console.log(str); // $script ```

String.split

切割字符串,将字符串转换为数组最常用的的方法

语法

stringObject.split(separator,howmany)

js const str = "hello world!"; const reg1 = /l/g; console.log("String.split.reg :", str.split(reg));

String.match

match() 方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。

语法

stringObject.match(searchvalue) stringObject.match(regexp)

js const str = "hello world!"; const reg = /l/; console.log("String.match.reg", str.match(reg));

String.search

search() 方法也接受字符串作为搜索参数。字符串参数将被转换为正则表达式:

语法

stringObject.search(regexp)

返回值

stringObject 中第一个与 regexp 相匹配的子串的起始位置。

注释:如果没有找到任何匹配的子串,则返回 -1。 var str = "Visit W3School!"; var n = str.search("W3School");

RegExp.test

test() 是一个正则表达式方法。它通过模式来搜索字符串,然后根据结果返回 true 或 false。

语法

RegExpObject.test(string)

js const str = "hello world!"; const reg = /l/; console.log("RegExp.test.reg:", reg.test(str));

RegExp.exec

exec() 方法用于检索字符串中的正则表达式的匹配。

语法

RegExpObject.exec(string)

返回值

返回一个数组,其中存放匹配的结果。如果未找到匹配,则返回值为 null。

js const str = "hello world!"; const reg = /l/; console.log("RegExp.exec.str:", reg.exec(str));

总结

其实除了一些特殊的项目,很少会有遇到经常需要去写正则表达式的地方,而整理这篇文档的目的也不是为了鼓励大家去手写正则表达式,正如我的标题,能写会看就行了,至少要会看。

正常情况下,遇到需要使用正则表达是的场景,正常的开发都会去上网搜一下现成的,而且90%以上的场景都能搜到,但是这个正则是否满足你的使用场景?是否你们的场景有特殊要求?这就需要开发人员能够基于借鉴过来的正则表达式进行二次开发,或者照着改一个出来了!

另一方面,一些简单的场景,例如 replace 的场景,一般来说都可以顺手写一个出来的,这些场景再去频繁的百度,就有点过分了。

希望对大家有用,希望这次总结能在脑子里面留痕。

参考文档