愿世间再无人不懂正则
当你去寻求帮助时,大佬甩给你的一句“用正则啊”,你是否会一脸无奈?
当你做合法校验时,打开谷歌搜索正则基本语法时,你是否会自责自怪?
当你 codereview 时,几行正则映入一心挑刺的你眼帘中,你是否会顿感语塞?
“正则没必要学,遇到的时候再查就行了。”
???
同志们,快醒醒,我们的目标是星辰大海,不是拧螺丝啊。
从明天起,做一个幸福的人,喂马、劈柴,学会正则。
正则表达式是对字符串操作的一种逻辑公式,它使用一些描述性的语言来表达对字符串的一种匹配策略,以实现对字符串的查找、校验、提取以及修改等目的。
要不再看看基本语法?
佛说:“每一次信誓旦旦的背后,都有一次看完基本语法就结束的正则表达式学习之旅。”
或许只有你,懂得我,所以你知道,正则有两种创建方式
字面量
字面量的方式由包裹在两个斜杠内的模式组成
const reg = /ab+c/
正则表达式字面量在 JS 脚本加载后就会被编译。
构造函数
调用 RegExp 对象的构建函数生成
const reg = new RegExp("ab+c")
用构造函数创建的正则表达式会在 JS 脚本运行过程中被编译,所以如果你的正则是动态地产生,推荐使用构造函数来创建。
不管是以前,还是现在,亦或是未来,我们知道了正则的组成结构
斜杆 + 匹配的模式 + 斜杆 + 修饰符/ pattern / flags
先说简单的修饰符。
修饰符
修饰符一般也被称为标记(flags),用于修饰出特定的匹配策略。
常见的修饰符有:
标志 | 描述 |
---|---|
i | ignore,将匹配策略标记为忽略大小写 |
g | global,查找所有的匹配项 |
m |
mult-line,使边界字符^ 和$ 匹配每一行的开头和结尾
|
s |
修饰. 点运算符(详见后文),加上 s 修饰符之后, . 可匹配包括换行符内的任意字符
|
而至于匹配模式,除了包含普通字符 ‘abc’ 、‘中国’、 ‘123’等,剩下的就不得不提那些在正则中发光发彩的特殊字符,她的名字,叫做小薇,哦不对,是元字符。
元字符
元字符并不代表他们本身的字面意思,他们都有特殊的含义。一些元字符写在方括号中的时候有一些特殊的意思。
元字符 | 描述 |
---|---|
. | 匹配除换行符外的任意单个字符 |
[ ] | 字符集,匹配方括号内的任意字符 |
[^ ] | 负值(否定)字符集,匹配除了方括号里的任意字符 |
* | 匹配前面的子模式出现 ≥0 次 |
+ | 匹配前面的字模式出现 ≥1 次 |
? | 匹配前面的子模式出现 0 次或 1 次 |
{n,m} | 匹配 num 个大括号之前的字符或字符集 (n ≤ num ≤ m) |
(xyz) | 字符集,匹配与 xyz 完全相等的字符串 |
| | 或运算符,匹配符号前或后的字符 |
\ |
转义字符,用于匹配一些保留的字符 [ ] ( ) { } . * + ? ^ $ \ |
|
^ | 脱字符,匹配字符开始处 |
$ | 美元符,匹配字符结尾处 |
.
点运算符
.
匹配除换行符(\n
、\r
)之外的任何单个字符。 比如:
'The car parked in the garage.'.match(/.ar/g)
// .ar 匹配一个任意字符后面跟着是 a 和 r 的字符串
// ['car', 'par', 'gar']
如果需要匹配包括\n
在内的任何字符,可以使用/.|\n)/
的模式,或者使用修饰符s
(详见上文修饰符章节)。
字符集
方括号[]
用来指定一个字符集,可以使用连字符-
来指定字符集范围([12345abcd]
→ [1-5a-d]
),字符集的顺序无关。比如:
'The car parked in the garage.'.match(/[tT]he/g)
// [tT]he 匹配 t 或 T 开头,后面跟着 he 的字符串
// ['The', 'the']
需要注意,特殊字符在方括号内失去特殊意义,比如[(a)+]
将会匹配(
、a
、)
、+
这四个字符。
但如果特殊字符^
出现在方括号的开头时,表示这个字符集是否定的。
'The car parked in the garage.'.match(/[^c]ar/g)
// [^c]ar 匹配除 c 外后面跟着 ar 的字符串
// ['par', 'gar']
正则其实提供了常见的字符集简写:
简写 | 描述 |
---|---|
\d |
匹配数字: [0-9]
|
\D |
匹配非数字: [^\d]
|
\w |
匹配所有字母数字,等同于 [a-zA-Z0-9_]
|
\W |
匹配所有非字母数字,即符号,等同于: [^\w]
|
\s |
匹配所有空格字符,等同于: [\t\n\f\r\p{Z}]
|
\S |
匹配所有非空格字符: [^\s]
|
你可能没有见过极光出现的村落,也没有见过有人在深夜放烟火,但你可能见到过有人使用类似/(\d\D)/
这样的组合来匹配任意字符。
限定符
限定符用来限定正则匹配子模式的次数,*
、+
、?
、{n,m}
这些都是限定符。
简单点,说话的方式简单点,就是要想匹配上,就必须出现限定多次。
特别的爱给特别的你,{n,m}
还有个名字叫量词,注意逗号前后可没有空格,,
与m
可省略。
- 如果写作
{n}
,表示匹配固定次数n
'The number was 9.9997 but we rounded it off to 10.0.'.match(/[\d]{3}/g)
// [\d]{3} 匹配 3 位数字
// ['999']
- 如果写作
{n,}
,表示至少匹配n
次
'The number was 9.9997 but we rounded it off to 10.0.'.match(/[\d]{1,}/g)
// [\d]{1,} 匹配至少 1 位数字,等同于 **[\d]+,**类似的 {0,} 就等同于 *****
// ['9', '9997', '10', '0']
- 如果写作
{n,m}
,表示匹配至少 n 次最多 m 次
'The number was 9.9997 but we rounded it off to 10.0.'.match(/[\d]{2,3}/g)
// [0-9]{2,3} 匹配最少 2 位最多 3 位数字
// ['999', '10']
()
特征标群
特征标群是一组写在圆括号()
中的子模式,其中包含的内容将会被看成一个整体。
()
在一定意义上作为衡量对正则的掌握水平的一个侧面标准,它提供了分组,正则玩得转其实很多的功能都是基于此展开。
分组
表达式 (ab)*
匹配连续出现 0 或更多个 ab
。如果没有使用 ()
,那么表达式 ab*
将匹配连续出现 0 或更多个 b
。
'ababa abbb'.match(/(ab)*/)
// (ab)* 匹配连续出现 ab 的字符串,其中结果的第二个元素 ab 表示匹配到符合 (...) 的结果
// ['abab', 'ab', index: 0, input: 'ababa abbb', groups: undefined]
对分组的引用一般就两种场景:
在 JS 里引用
我们使用正则除了验证数据合法性之外,另一个大场景就是数据提取与替换了。
- 数据提取
const reg = /(\d{4})-(\d{2})-(\d{2})/
// 1. 使用不带修饰符 g 的正则 match
'2021-12-31'.match(reg)
// 返回的数组,第一个元素是整体匹配结果,然后在 index 前的就是捕获到的各分组(括号)匹配的内容
// ['2021-12-31', '2021', '12', '31', index: 0, input: '2021-12-31', groups: undefined]// 2. 使用构造函数的全局属性 $1 至 $9 来获取
reg.test('2021-12-31')
// RegExp.$1 -> '2021', RegExp.$2 -> '12', RegExp.$3 -> '31'
- 数据替换
'2021-12-31'.replace(/(\d{4})-(\d{2})-(\d{2})/, function(match, year, month, day) {return month + "/" + day + "/" + year
})
// year, month, day 则按顺序分别代表第 n 个括号匹配的字符串
// '12/31/2021'
在正则表达式中引用
不同于使用 API 来引用分组,在正则表达式本身里直接引用分组的方式称为反向引用。
比如要写一个正则支持匹配如下三种格式:
- 2021-12-31
- 2021/12/31
- 2021.12.31
const reg = /\d{4}(-|\/|\.)\d{2}\1\d{2}/
// \1 表示的引用之前的那个分组 (-|\/|\.),其中因为 / 和 . 需要转义,前面加符号 \
// 不管它匹配到什么(比如-),\1 都匹配那个同样的具体某个字符
// regex.test('2021-12-31') // true
// regex.test('2021-12.31') // false
需要注意,如果引用了不存在的组,正则并不会报错,而是匹配字符本身。比如/\1\2/
就表示匹配\1
\2
。
非捕获型分组
()
匹配产生的分组不管是使用 match 亦或是 构造函数等,都将记录每个()
匹配的结果,所以一般也称他们为捕获型分组。当然,这必然会增加开销,对性能与效率产生或多或少的影响。
所以如果我们只是单纯的想使用括号最原始的功能,但不引用它,即:既不在API里引用,也不在正则里反向引用。那么我们就可以使用非捕获分组(?:p)
。
'ababa abbb'.match(/(?:ab)*/)
// (?:ab)* 仍匹配连续出现 ab 的字符串,作用同 (ab)*,但与上文分组章节的相比,结果中不再记录捕获结果
// ['abab', index: 0, input: 'ababa abbb', groups: undefined]
分支结构
()
另一个比较重要且常用的功能就是使用符号|
表示或,构成分支结构,所以|
也被称为或运算符。
'The car is parked in the garage.'.match(/(T|t)he|car/g)
// (T|t)he|car 匹配 (T|t)he 或 car
// ['The', 'car', 'the']
\
特殊字符转义
如果想要匹配{ } [ ] () / \ + * . $ ^ | ?
这些特殊字符则要在其前面加上反斜线 \
。
'The car is parked in the garage.'.match(/\w+e\.?/g)
// /\w+e\.? 匹配以 e 或 . 结尾的字符串
// ['The', 'parke', 'the', 'garage.']
锚点
在正则表达式中,想要匹配指定开头或结尾的字符串时使用到锚点,^
指定开头,$
指定结尾。
当我们需要对一个字符串进行校验的时候,比如校验手机号码、身份证号码、密码等等,就需要加上^
和$
来确保完整的字符串开头结尾都被校验到。
而如果只是匹配替换数据的话,一般是不需要的,因为我们要匹配的数据往往有可能出现在一大串文本中的任何位置上。
位置位置位置
正则表达式是匹配模式,要么匹配字符,要么匹配位置。
那老姚对我说,说我是一个小偷,偷他的回忆,塞进我的脑海里。
不得不说,搞懂位置对我们使用正则进行查找替换的帮助不要太大。
其实说到位置,除了前面提到的前后锚点^
与$
之外,还有更多锚点:
符号 | 描述 |
---|---|
\b | 匹配单词边界 |
\B | 匹配非单词边界 |
?= | positive lookahead,正向先行断言, |
?! | positive lookahead,负向先行断言 |
?<= | positive lookabehind,正向后发断言 |
?<! | negative lookbehind,负向后发断言 |
\b
和\B
我就想让你翻译翻译,什么叫单词边界?
\w
和\W
之间的位置^
与\w
之间的位置\w
与$
之间的位置
'[Regex] Lesson_01.mp4'.replace(/\b/g, '#')
// \w 匹配 [a-zA-Z0-9_],所以结合上述单词边界的定义,可以得出答案
// '[#Regex#] #Lesson_01#.#mp4#'
理解了\b
,再看\B
就很好理解了。
'[Regex] Lesson_01.mp4'.replace(/\B/g, '#')
// 与上例 \b 的结果完全相反
// '#[R#e#g#e#x]# L#e#s#s#o#n#_#0#1.m#p#4'
零宽断言
先行断言和后发断言(合称 lookaround)都属于非捕获组(所以需要使用()
)。当我们需要在匹配模式的前面或后面有另一个特定的模式时,就可以使用它们。
(?=pattern)
正向先行断言
匹配 pattern 之前的位置,即:要想满足匹配,后面得跟着 pattern 。
'The fat cat sat on the mat.'.match(/(T|t)he(?=\sfat)/g)
// (T|t)he(?=\sfat) 匹配在 \sfat 前的 The 或 the,\s 表示空格
// ['The']
(?!pattern)
负向先行断言
匹配不含 pattern 之前的位置,即:要想满足匹配,后面不能跟着 pattern 。
'The fat cat sat on the mat.'.match(/(T|t)he(?!\sfat)/g)
// (T|t)he(?!\sfat) 匹配不在 \sfat 前的 The 或 the,\s 表示空格
// ['the']
(?<=pattern)
正向后发断言
匹配 pattern 之后的位置,即:要想满足匹配,前面得跟着 pattern 。
'The fat cat sat on the mat.'.match(/(?<=(T|t)he\s)(fat|mat)/g)
// (?<=(T|t)he\s)(fat|mat) 匹配在 The\s 或 the\s 后面的 fat 或 mat,\s 表示空格
// ['fat', 'mat']
(?<!pattern)
负向后发断言
匹配不含 pattern 之后的位置,即:要想满足匹配,前面不能跟着 pattern 。
'The cat sat on cat.'.match(/(?<!(T|t)he\s)(cat)/g)
// (?<!(T|t)he\s)(cat) 匹配在 The\s 或 the\s 后面的 cat,\s 表示空格
// ['cat'']
偷偷告诉你一个我发现的规律:
- 所谓”正”,即字符中需要出现 pattern
- 所谓“负”,即字符中不能出现 pattern
- 所谓“先”,即匹配在 pattern 前的位置
- 所谓“后”,即匹配在 pattern 后的位置
别贪了,再贪我就是狗
我们都知道,止损重要,止盈同样也很重要,它指当盈利时如遇下跌及时出局,保住一定利润。这当然就需要我们做到“不要太贪”。
那么,我们具体要如何做好止盈呢?
(好像有点跑题
愿世间再无人不懂正则相关推荐
- 详解Oracle架构、原理、进程,学会世间再无复杂架构
详解Oracle架构.原理.进程,学会世间再无复杂架构 学习是一个循序渐进的过程,从面到点.从宏观到微观,逐步渗透,各个击破,对于Oracle, 怎么样从宏观上来理解呢?先来看一个图,这个图取自于教材 ...
- 愿天下团圆,愿天下再无团圆
1 韩旭杰的人生,在1977年差点被终结. 那天他的母亲因为青霉素过敏,躺在床上休息,父亲在单位上班. 只有1岁多的韩旭杰刚学会走路,一个人跌跌撞撞从房间爬到了院子里. 起初,韩旭杰的母亲还能听到孩子 ...
- 世间再无霍金,时间永留简史!
(点击上方蓝字,快速关注我们) 综合转自:澎湃新闻.中新网.新华网.凤凰网.知乎等 3月14日消息,据 BBC 报道,著名物理学家史蒂芬·霍金去世,享年 76 岁.霍金家人发表声明,称他的勇气与坚持鼓 ...
- 愿世间项目再无node-sass
- 万字详解Oracle架构、原理、进程,学会世间再无复杂架构
学习是一个循序渐进的过程,从面到点.从宏观到微观,逐步渗透,各个击破,对于Oracle, 怎么样从宏观上来理解呢?先来看一个图,这个图取自于教材,这个图对于从整体上理解ORACLE 的体系结构组件,非 ...
- 关于K-SVD算法中逐列更新的目标函数的理解,再看不懂就打死我吧
K-svd采用逐列更新的方法更新字典,就是当更新第k列原子的时候,其它的原子固定不变.假设我们当前要更新第k个原子αk,令编码矩阵X对应的第k行为xk,则目标函数为: 一直没看明白这个表达式,刚才通过 ...
- 当摸鱼的老油条遇上了内卷的小年轻...愿世间没有内卷....
在程序员职场上,什么样的人最让人反感呢? 是技术不好的人吗?并不是.技术不好的同事,我们可以帮他. 是技术太强的人吗?也不是.技术很强的同事,可遇不可求,向他学习还来不及呢. 真正让人反感的,是技术平 ...
- Tcp三次握手、四次分手,Socket再看不懂,你砍我
文章目录 Tcp连接 三次握手 为什么要三次 四次分手 光说不练,假把式 三次握手.四次分手抓包 三次握手到四次分手是不可被分割的最小粒度 Socket 文件描述符 获取输入输出流 socket套接字 ...
- 此时已莺飞草长,愿世间美好与你环环相扣
文章目录 我是诗人,你是落入凡尘的天使:我的爱毫无保留,你为我坠入凡尘. 一起走过的那些日子,像是幻影却又如此刻骨铭心 代码表白,此时此刻你是我心中的唯一 我希望,这世间美好永远与你环环相扣 我是诗人 ...
最新文章
- 我就不信看完这篇你还搞不懂信息熵
- 浪潮as5300技术方案_混闪存储AS5300G5
- OpenCV中cvWaitKey()函数注意事项
- 两个datatable的比较
- C++安全方向(三)3.4 使用哈希列表验证文件的完整性
- AIoT 又迎一利器,涂鸦云开发平台来了!
- 十大热门的 JavaScript 框架和库
- 带你看JDK源码之HashMap
- 国外ipv6服务器网站,国外 ipv6 服务器地址
- 独木难成林,不管是自己在支付宝单种还是钉钉合种,都是在做公益
- 养肾=养命!这5个养肾方法,程序员都保存好了!
- Unity3D说明文档翻译-Audio Manager
- 应用电路笔记(1)-三极管8550和8050应用
- 【STL】STL函数总结,助你代码实用高逼格
- 测试/开发程序员值这么多钱么?“我“不会愿赌服输......
- L1-002打印沙漏C语言,沙漏
- 三类医疗器械经营许可证
- ContentProvider总结与简单Demo
- 技嘉1080显卡体质测试软件,【技嘉GTX1080评测】突破屏障 技嘉GTX 1080 G1 Gaming评测_技嘉 GTX 1080 G1 Gaming 8G_显卡评测-中关村在线...
- Ubuntu 电脑下插入移动硬盘,显示不能挂载该硬盘