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

lxf2023-03-14 13:13:01

开启AdminJS成长之旅!这是我参与「AdminJS日新计划 · 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 位之间。

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

/**
 * /^ 开始
 * [] 查找方括号中间的任何字符
 * 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 和 手机号 验证

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

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

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

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

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

身份证号和url验证

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

// 身份证号(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 年之间,不太合理。

// 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)

一些小技巧

// 替换所有,对 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)  
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)
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)
const str = "hello world!";
const reg = /l/;
console.log("RegExp.test.reg:", reg.test(str));

RegExp.exec

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

语法

RegExpObject.exec(string)

返回值

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

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

总结

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

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

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

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

参考文档

  • 15个常用的javaScript正则表达式(收藏)
  • JavaScript 正则表达式 | 菜鸟教程
  • JavaScript replace() 方法
  • js replace方法