厉害!这篇正则表达式竟写的如此详尽

10,796 阅读9分钟

政采云技术团队.png

伟豪.png

这是第 156 篇不掺水的原创,想获取更多原创好文,请搜索公众号关注我们吧~ 本文首发于政采云前端博客:厉害!这篇正则表达式竟写的如此详尽

前言

为什么要学正则表达式?

作为一个菜鸟程序员,遇到复杂的正则时往往会求助搜索引擎。虽然能够解决燃眉之急,但往往会有边界值和团队正则不统一的问题。而这种问题往往会被细测测出来,造成不必要的麻烦。对于这种情况,最好的解决方式就是去吃透它,最终能将它手写出来。所谓知其然,也要知其所以然。

RegEx.jpeg

什么是正则表达式?

1951年,一位名叫史蒂芬·克林(Stephen Kleene)的数学科学家,发表了一篇名叫《神经网事件的表示法》的论文。论文中引入了正则表达式的概念。直至七十多年后的今天,正则表达式仍然影响着我们互联网生活的方方面面。

创建密码:.jpg

比如需要填入符合特定规则的密码、手机号、邮箱等。 9f78f90f4fc7eea055079baf1e0b608d.jpg

又比如我们需要对输入框加一个中文、英文或价格等输入限制

不过在我们学习正则表达式之前,我们需要先熟悉下它的调试工具

怎么检测我的正则对不对呢?

编辑器内置的搜索工具

我们可以使用Vscode提供的正则的搜索方法,来使用正则匹配我们需要搜索的内容。

2bd784719bc5b6978b666890e9f11b50.jpg

函数调用

也可以在自己熟悉的编程语言中调用函数去测试。

eba976de552d2f27a5fa4488610a6fd4.jpg

在线测试网站

亦或是使用我这边推荐的一个在线测试网站

9bc0825470062a225ba48862618cef84.jpg

了解了调试工具后我们开始从基础学习吧。

基础

限定符(Quantifiers)

?

782b5df53d9f928c3665e4ad921432d1.jpg

“?”表示前面的字符可以出现1次或者0次。说简单点就是"co"中的"o"这个字母可有可无。

+

5f90add12862e68dac52415793a22110.jpg

“+”表示前面的字符至少匹配1次或多次。比如上列中”poverty“、“poor”得到了匹配。“premier”却没有匹配。

*

729092bb5629b191be2acc9a44d14cf7.jpg

“*”可以匹配0个或者多个字符。可以看到出现 0 次的“pr”和 2 次的“poor”得到了匹配。我们如何匹配固定次数一定范围的字符呢?

{...}

39a63a55e7025e5452963a83ce2aafed.jpg

“{...}”可以用来匹配固定数量的字符或某个范围内的字符。

如上图所示:

  • “{2}”当只有一个参数时,只匹配固定数量。

  • “{2,3}”当有两个参数时,匹配范围内的。

  • “{2,}”当后面的参数为空时,匹配n个及以上。

(...)

以上都讲的是匹配单个字符,当我们想匹配多个字符该怎么办呢?

104f053573d0205258cc98118a8b2619.jpg

我们可以将我们需要匹配的字符用“(...)”括起来,可以看到“(...)”搭配我们上文所学的“+“成功匹配了”banana“和”anna“。

或运算符(OR Operator)

c5dae76af93356ac4ed278c7815e1f59.jpg

当我们需要匹配两个或多个条件时我们就需要使用"|"(或运算符)。图中我们匹配了拥有“an”或“na”的单词。值得注意的是这里的“|”最外层的"()"是必不可少的。

字符类(Character class)

1458bf56eb5d965823ace88ef27a91ec.jpg

比如我们要匹配拥有“123”中任意字符的字符串则需要使用“[...]”,需要匹配的字符只能取自方括号中的内容。另外我们也可以使用字符类去匹配指定范围,如[a-z]、[0-9]、[\u4e00-\u9fa5]等。

708c3df8641ef81355556e12cceae373.jpg

另外我们也可以在前面加”^“用来匹配非此范围的字符串,如上我们就匹配了非英文的字符串。

另外正则表达式也提供了很多元字符可以让我们简写我们的正则表达式。

元字符(Meta character)

b7f5812a8a3593cb255f3540f0f372bf.jpg

我们可以使用“\d”代替我们之前的“[0-9]”,这里还有两个特殊字符“^”会匹配开始(这里要注意与我们前面讲到的字符类中的运用要区分),“$“会匹配结束。

  • \d,\w,\s - 匹配数字、字符、空格(分别代表着:digit、word、space)。
  • \D,\W,\S - 匹配非数字、非字符、非空格。
  • . - 除换行符以外的所有字符(句号句子的结束符)。
  • ^ - 字符串开头。
  • $ - 字符串结尾。

接下来我们来学习一下正则表达式的高级概念

高级概念

懒惰匹配和贪婪匹配(Lazy and Greed)

当我们想要匹配文本中的HTML时我们会写下如下正则表达式。

b75cdf169ee888acb769c77838b8dd80.jpg

根据前面的知识我们知道,此正则将会匹配<开头>结尾中间可以有至少1个任意字符。但结果是匹配了“<”和“>” 标签包含的所有内容。

cc5d409da9aa40792ba90e5b537ebb35.jpg

我们可以在正则表达式中加“?”来开启懒惰匹配。开启懒惰匹配后,正则会尽可能少的匹配。所以当匹配的时候发现标签 “ <div> ”已经是符合要求的,所以会匹配<div>标签,然后继续向下匹配,发现</div>标签也是符合要求的,继续向下匹配,发现文字不符合要求,继续向下,发现<span>和</span>标签都是符合要求的,所以最后会匹配 <div>, </div>, <span>, </span>四个标签。

分组(Group)

前面我们认识了“(...)”的用法,将其结合我们后面所学的知识就是正则的分组

a7392ff6dd264803a4452a4df3e8f920.jpg

如上图所示第一个分组中我们匹配 4 个数字,第二个分组中我们匹配 7 个数字。中间使用“-”进行连接,便很容易匹配到了文本中出现的座机电话号。

非捕获分组(Non-capture Group)

非捕获分组:(?:表达式),分组匹配之后,不需要的用“?: ”语法过滤子表达式内容。也就是代码匹配,但是不保存。

在使用非捕获前:

bd7414a425996817887ea8ff34768dec.jpg

在使用非捕获后:

29711c018bc8d6c196613e0ed930e554.jpg

通过.exec方法并没有捕获到月份。

回溯(Flash Back)

当我们想匹配一个正确的HTML标签时,使用"<[\w]+>.*<\/[\w]+>"。

d974356d2305441e19234b3de6a402e0.jpg

可以看到虽然可以匹配HTML开始和结束标签,但是却不能校验前后的一致性。如“</span>”并不是“<div>”的结束标签。

38a9b1c3218a7a0642d44772f0e63d90.jpg

我们可以把后面的部分改成“<\/\1>”其中“\1”就是引用第一个分组。这样一来我们就可以匹配正确的HTML标签了。

断言(Assertion)

断言有些地方也叫环视(Lookaround),它只进行子表达式的匹配,不占有字符,匹配到的内容不保存到最终的匹配结果。

正向先行断言

正向先行断言:(?=表达式),指在某个位置往右看,所在的位置右侧必须匹配表达式

c16a1e3118f7225c9c5b109c4a1df01f.jpg

我们可以看到“/喜欢(?=你)”正确匹配到了“你”前面有“喜欢”的文本。

实现一个密码强度校验

至少有一个大写字母。 至少有一个小写字母。 至少有一个数字。 至少有8个字符。

3b6d35aa5cefc456ebc5ef02b412f44a.jpg

反向先行断言

反向先行断言:(?!表达式),指在某个位置往右看,不能存在表达式中的内容。

f324acb154bd9b76c8f3e05f8167e23e.jpg

如上就排除了“喜欢”后面有“你”的字符串。

正向后行断言

正向后行断言:(?<=表达式),指在某个位置往左看,存在表达式中的内容。

84cea0dfb73d0222db6baec62fbeb2be.jpg

如上就匹配了“喜欢”前面有“我”的字符串。

反向后行断言

反向后行断言:(?<!表达式),指在某个位置往左看,不能存在表达式中的内容。

d853e7b39730aa7648879a717647ad66.jpg

如上就排除了“喜欢”前面有“我”的字符串。

至此正则表达式的高级部分学完啦,接下来我们学习正则提供的方法

方法

在JavaScript中,RegExp对象是一个预定义了属性和方法的正则表达式对象。

test()

640.png

该方法用于检测一个字符串是否匹配某个正则表达式,匹配返回 true,不匹配返回 false。

exec()

94254b2d0aa9579258f63de420322fd4.jpg

该方法用于检测字符串中对正则表达式的匹配。

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

除了正则自身携带的方法,配合 String 对象的方法一起使用也会有额外的效果。

配合String提供的方法

match

match 这个方法主要用来提取数据,它配合分组的()一起使用,可以很方便的提取数据。

var str = '2022-04-22'
var reg = /^(\d{4})-(\d{2})-(\d{2})$/
console.log(str.match(reg));
//  ['2022-04-22', '2022', '04', '22', index: 0, input: '2022-04-22', groups: undefined]

replace

replace这个api主要用于替换数据,多用于字符串的处理和转义。

var str = '贾维斯:您今天共产生了8个BUG'
var reg = /\w{3}/g
console.log(str.replace(reg,"Beautiful Code"))
// 贾维斯:您今天共产生了8个Beautiful Code

什么是 $1 $2

let str = "前端1组-开发部";
console.log(str.replace(/(.{4})-(.{3})/, "$2 $1"));
// 开发部 前端1组

$1,$2上就是按顺序对应小括号里面的分组 捕获到的内容。这里我们将 2组和1组进行内容替换,就得到了替换后的内容。

split

split主要用于来切分字符串为数组,它的第一个参数也可以为正则的形式。

const str1 = '2022-04-21'
const str2 = '2022.04.22'
const str3 = '2022/04/23'
const regsSplit = /[\.\-\/]/
console.log(str1.split(regsSplit))
console.log(str2.split(regsSplit))
console.log(str3.split(regsSplit))
// ['2022', '04', '21']
// ['2022', '04', '22']
// ['2022', '04', '23']

了解完了结合String用法,我们再来了解一下兼容性问题

正则表达式兼容性调研

在我们日常使用中,一定会遇到兼容性问题。这里主要对一些不完全兼容的方法进行调研。

@@split

@@split 方法切割String对象为一个其子字符串的数组 。

var re = /-/g;
var str = '2022-01-02';
var result = re[Symbol.split](str);
console.log(result);  // ["2022", "01", "02"]

兼容性

5c998fd1bb7ed1f4f1edf47892b674bc.jpg

@@match

对正则表达式匹配字符串时,@@match 方法用于获取匹配结果。

var re = /[0-9]+/g;
var str = '2022-01-02';
var result = re[Symbol.match](str);
console.log(result);  // ["2022", "01", "02"]

兼容性

fb392fd5814bd1a403776ff6cd3947fe.jpg

@@search

@@search 方法执行了一个在给定字符串中的一个搜索以取得匹配正则模式的项。

var re = /-/g;
var str = '2016-01-02';
var result = re[Symbol.search](str);
console.log(result);  // 4

兼容性

01a09f895f172c2492ec4c70901e20aa.jpg

@@replace

@@replace 方法会在一个字符串中用给定的替换器,替换所有符合正则模式的匹配项,并返回替换后的新字符串结果。用来替换的参数可以是一个字符串或是一个针对每次匹配的回调函数。

var re = /-/g; 
var str = '2016-01-01';
var newstr = re[Symbol.replace](str, '.');
console.log(newstr);  // 2016.01.01

兼容性

6529acfb1c3fd4665e0bf9ddfe2ce6b6.jpg

flags

flags 属性返回一个字符串,由当前正则表达式对象的标志组成。

/foo/ig.flags;   // "gi"
/bar/myu.flags;  // "muy"

兼容性

921a751282a73cda11768b842bbbd01b.jpg

dotAll

正则中的点匹配就是 dotAll ,都是匹配任意字符,但是很多字符是无法匹配的。例如:

  • 四个字节的 UTF-16 的字符
  • 行终止符 \n \r 换行 回车
console.log(/foo.bar/.test('foo\nbar'))
// false
console.log(/foo.bar/.test('fooabar'))
// true

加上 s 可以匹配换行符

console.log(/foo.bar/s.test('foo\nbar'))
// true

兼容性

cfda85f9c82bb1c904951d7744167fb6.jpg

最后我们让来点干货

干货

保留两位小数的价格输入框

具体代码如下:


// 输入限制
const changePiece = (e) =>{
      e.target.value = e.target.value.replace(/^\D*(\d*(?:\.\d{0,2})?).*$/g, '$1');
  }
  return (
    <div>
       <input type="text" onKeyUp={ (e) => {changePiece(e)}} /> 
    </div>
  );

常用正则表达式

// 手机号码的校验
const phoneReg = /^[1][3,4,5,6,7,8,9][0-9]{9}$/
// 身份证的校验
const idCardReg = /^[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]$/
// URL的校验
const urlReg = /^((https?|ftp|file):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/
// 邮箱的校验
const emailReg = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/
// 日期 YYYY-MM-DD 校验
const dateReg = /^\d{4}(\-)\d{1,2}\1\d{1,2}$/

以上干货大家也可以通过上文所学的知识自己尝试实现,自己实践才会有更深刻的印象和更深度的认知。

参考

《Can I Use——正则表达式》

《有了这25个正则表达式,代码效率提高80%》

《10分钟快速掌握正则表达式》

推荐阅读

学习 HTTP Referer

浅谈低代码平台远程组件加载方案

前端富文本基础及实现

可视化搭建系统之数据源

表单数据形式配置化设计

开源作品

  • 政采云前端小报

开源地址 www.zoo.team/openweekly/ (小报官网首页有微信交流群)

  • 商品选择 sku 插件

开源地址 github.com/zcy-inc/sku…

招贤纳士

政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 90 余个前端小伙伴,平均年龄 27 岁,近 4 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推动并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。

如果你想改变一直被事折腾,希望开始能折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变既定的节奏,将会是“5 年工作时间 3 年工作经验”;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 ZooTeam@cai-inc.com