javascript中的正则

2019 / 07 / 28

前言

正则表达式简称为正则,是很多前端同学的弱项,但作为一个非常古老而基础的字符串处理方式,它可能比javascript的年龄都要大很多。它并不附属于某个学科,而是一种编程中处理字符串的逻辑。处理字符串是一件非常常见的工作,因而它的价值也是毋庸置疑的,把它比作成瑞士军刀是一个很贴切的比喻,正则就像一把锋利便捷的军刀,解决问题直击要害,锋利尖锐。

我见过非常多的人学不会正则,或者学正则学的痛哭流涕也不知道如何下手。首先你需要认为它很简单,然后去知道它的一些用法,其次就像尽量多的去使用它,当能使用它解决问题的时候,请不要轻视它,或者不要觉得它简单,应该认真起来,去非常系统的补漏所有的点,然后尽量巧妙而且正确的使用它,这个时候肯定可以上一个台阶的。

定义

正则表达式(Regular Expression) 是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个规则字符串,这个规则字符串用来表达对字符串的一种过滤逻辑。

正则由两部分 pattern和flags

flags

flags标示正则的修饰符,包括igmyus,含义分别是

i 区分大小写

g 全局匹配

m 多行匹配

y 匹配从固定的lastIndex开始,类似于^

u 很少用,可以匹配4字节的unicode编码

s dotAll模式,点可以匹配换行符

igm是js正则初代便拥有的能力,y是es6的新特性,us是es9的特性。

pattern

pattern是正则的匹配逻辑,由各种各样的符号组成

包括一些常用的工具符和转义表达式。

.表示任意字符,+表示至少一个,*表示0个或多个,?表示见好就收,\ 转义,^从头开始,也有非的含义,$表示匹配结尾。| 或者,中括号[]表示类,可以理解为and,{}表示数量,()表示分组,中划线-可以表示类的区间,比如b-k,3-7,也只能在类中使用。

除了操作符还有一些常用的转义表达式。这些可以理解为正则定义好的类,比如[1234567890]这个组可以简写为\d。而[^1234567890]可以简写为\D

\d数值 \D非数值 \s 空白自负 \S 非空白字符 \w 字母数字下划线 \W 非数字字母下划线。

正则的一些概念

一下手就记很多的符号对于工程师来说一定比较头疼。所以我们按照常识来思考正则的这些知识点,首先正则为了匹配字符设置了一个叫做类的概念,这个类和面向对象里的类完全不是一个东西,可以理解为是一个新的正则概念,比如\d的意义就是数字,那么\d就是一个类。

比如我需要新的概念,数学运算相关字符,那么就能定义为[\d+-*/]表示的就是数值和加减乘除的这类字符串。

  1. 正则中写数值可以为[0123456789]也可以是\d,还可以写作[0-9],同样所有的字母可以写作[a-z]。中划线只能用在类中,在[]的外边就是一个普通字符。中划线-可以表示类的值的区间。
  2. 一个类每次只能匹配一个字符,通俗的来讲 /a/ 中的a也是一个类,表示匹配字母a,等价于[a]。
  3. .是一个最强大的类,可以匹配任何字符,在dotAll模式下,.可以匹配包括换行符,制表符的一切字符,.和\d一样也知识一种简写。
  4. 类中的+-*/都不需要转译,除了转义符之外的都会被视为普通字符,当然 - 这个字符是有含义的,但是只要不是表示正则可以判断出来他是在表示区间还是普通字符。
  5. 类中的^表示非,比如非数值可以用[^0-9]来表示。

正则匹配的基本单位是类,用来匹配对应的一个字符。基本的写法是[]。

有了类的基础,我们就可以匹配所有的字符了,但是我们只能匹配一个字符。必须借助计量工具,最开始我们就认识的*和+就是一个计量工具,比如\d+表示 一位或者多位的数字。

但最好先忘了*和+这两个工具,量词是通过{}来表示的。比如

{0,} 0次或者多次,等价于*

{1,} 1次或者多次,等价于+

{0,1} 0次或者1次,等价于?
{n} 表示n次,

{n,m}n到m次

{n,} 大于等于n次

量是一个很简单的东西,但记住常见的简写?+*是很又必要的

关于量,还有一个概念,叫做贪婪和非贪婪,这个是用?来实现的,比如.*会匹配所有的字符,但是.*?就是尽量少的匹配,所以会匹配到一个空字符串。

边界

有了量和类就可以匹配大多数想要的东西了,但是有时候我们需要一个边界来限定匹配的位置,比如某个匹配必须要从头开始。

常见的边界有

^ 表示以xx开始,注意它在类中表示非。

$ 表示以xx结尾

\b 表示单词词尾

\B 表示非单词词尾

分组

分组是一个非常关键的技巧,也是正则里最重要和最复杂的概念。分组很普遍,比如匹配 hehe 的时候需要(he){2}来表示连续两个he。

和与或

分组中可以通过 | 来表示或的关系,比如 \.(com|cn)$ 表示.com或者.cn结尾。那么有的人会问即然有或,那么和应该是什么。关于这个问题,其实正则中默认都是和,什么意思呢,(he){2} 其实指是分组中h和e,但很多人默认理解成了he,这就导致。和的运算优先级高于或,分组的优先级大于和,分组中可以嵌套分组。

分组命名

es2019中支持了分组的命名,这样可以更方便的获取到某个分组的匹配值。

比如

const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
re.exec('2019-03-02').groups // { year: '2019', month: '03', day: '02' }

分组和捕获

如果说正则最大的能力是匹配,那么匹配最大的用处应该就是捕获了,而正则的捕获只和分组相关,这个怎么理解呢,可以认为没有分组就没有捕获。比如上边的demo里的groups就是捕获到的值,那么一个正则没有分组呢,不会的,每个正则本身就是一个分组。

那如果没有分组的命名,该怎么捕获呢,答案是索引。比如上边的demo的加上最外层最大的分组一共有四个分组。所以他的结果是个四个值的数组。

分组的索引是从外到内,从左到右标记的。

[
'2019-03-02',
'2019',
'03',
'02',
index: 0,
input: '2019-03-02',
groups: [Object: null prototype] { year: '2019', month: '03', day: '02' }
]

前瞻后顾

经常会出现的一个场景叫做,我要后边是xxx或者后边不是xxx,或者前边是xxx或者前边不是xxx,这个在正则中有一个华丽丽而且很有文学气息的名字,前瞻后顾。这个名字很绕,不介意花时间理解和记住,如果非要记的话可以理解为后顾是通过定义前边来推测后边,前瞻是通过定义后边来推测前边。

前瞻后顾从写法上来说也是略有一丢丢的复杂,理解起来也要比前面的东西复杂很多。

举个栗子,要写一个匹配价格的正则,那价格应该是¥开头的,那么就可以这么写

let re = /(?<=¥)\d+/
re.exec('The 2 apples is ¥10') // 10

这个就是后顾的用法,可以理解想要前边是¥的值。

当然这个不用后顾也很好实现,所以前瞻和后顾也只是一种更优的表达方式,就和转义表达式一样,并不是必须存在的。

let re =(?<price>\d+)/
re.exec('The 2 apples is ¥10').groups.price // 10

总结一下

?<=xxx 表示以xxx开头,用在分组开头

?<!xxx 表示不能以xxx开头,用在分组开头

?=xxx 表示以xxx结尾,用在分组末尾

?!xxx 表示不能以xxx结尾,用在分组末尾

注意,前瞻后顾都是用来修饰分组的,并不能匹配到具体的东西。

总结

以上便是javascript所用到的正则的全部了,但如果想要更好的使用正则的话还是需要学javascript的内置构造函数RegExp对象的特性。比如它接受第二个参数为flags,比如replace,exec,test函数,比如正则的lastIndex属性,比如字符串的match函数。exec函数很有趣会一个一个爬。

当然学会正则不难,重点是要用,要感受到他作为一把瑞士军刀是如何高效灵活的解决问题的,而不是放久了生锈了,面试的时候拿出来磨一下的那种。

嗨,请先 登录

加载中...
(๑>ω<๑) 又是元气满满的一天哟