Skip to content

JavaScript学习之正则表达式 #8

@superNever

Description

@superNever

JavaScript学习之正则表达式

背景

正则表达式是被用来匹配字符串中的字符组合的模式。在JavaScript中,正则表达式也是对象。这种模式可以被用于 RegExp 的 exec 和 test 方法以及 String 的 match、replace、search 和 split 方法。

声明一点

在使用正则表达式的时候,你将会发现几乎所有的问题都有不止一种解决方案。他们有的简单,又得比较快速,又得兼容性更好,有的功能更全。这么说吧,在编写正则表达式的时候,只有对、错两种选择的情况是相当少见的--同一个问题往往会有多种解决方案。

开门见山 - 创建一个正则表达式

在JavaScript中你可以有两种方式创建一个正则表达式:

  • 使用一个正则表达式字面量
    var reg = /\w?\d*(b[\w|\r])+c/;

正则表达式字面量在脚本加载后编译。若你的正则表达式是常量,使用这种方式可以获得更好的性能。

  • 调用RegExp对象的构造函数,如下所示:
    var reg = new RegExp("\d{3,3}-\d{3,3}-\d{4,4}");

使用构造函数,提供了对正则表达式运行时的编译。当你知道正则表达式的模式会发生改变, 或者你事先并不了解它的模式或者是从其他地方(比如用户的输入),得到的代码这时比较适合用构造函数的方式。

那么问题来了

正则表达式这些特殊的字符(元字符)到底有哪些,应该怎么记忆他们呢?形如. * ? {} []这些都是代表什么意思呢?

元字符

空白字符

声明一下,以下所有英文对照仅为个人理解,不具有任何权威性

元字符 说明 对照英文
[\b] 回退并删除 backspace
\f 换页符 form-feed
\n 换行符 line feed
\r 回车符 carriage returns
\t 制表符(tab) tab characters
\v 垂直制表符 vertical tab

数字字符

仅仅有两个如下:

元字符 说明
\d 任何一个数字字符(等价于[0-9])
\D 任何一个非数字字符(等价于[^0-9])

注意一点正则表达式中的大写字母都是对应的小写字符的,但不是补集,下面的都是遵循这个规则

字母数字元字符

元字符 说明
\w 任何一个字母数字字符(大小写均可)或下划线(等价于[a-zA-Z0-9_])
\W 任何一个非字母数字或非下划线字符(等价于[^a-zA-Z0-9_])

空白字符元字符

元字符 说明
\s 任何一个空白字符(等价于[\f\n\r\t\v])
\S 任何一个非空白字符(等价于[^\f\n\r\t\v])

注意 用来匹配退格字符的[\b]元字符是一个特例:它不在类元字符\s的覆盖范围,当然也就没有被排除在类元字符\S的覆盖范围外。这也就是上面我所强调的那句话,大写字母都是对应的小写字符的,而非补集

重复匹配

匹配一个或多个字符

要想匹配同一个字符或字符集合的多次重复,只需要简单地给这个字符或者字符集合加上一个+作为后缀就行了。栗子一颗:
a匹配a本身,a+将匹配一个或多个连续出现的a。类似地,[0-9]匹配任意单个数字。[0-9]+将匹配一个或多个连续的数字.

在给一个字符集合加上+后缀的时候,必须把+放在这个字符集合的外面。比如说,[0-9]+是正确的,[0-9+]则是错误的,虽然这样写是合法的但不是我们想要的匹配至少一个数字,而变成了由一个数字和一个加号组成的集合。

匹配零个或多个字符

与上文提到的+类似的是*,它不是匹配最少一个,而是匹配至少0个。
也就是可以这么理解“在我前面的字符或者字符集合是可选的”。

匹配零个或一个字符

?只能匹配一个字符或字符集合的零次或者一次,最多不超过一次。举个栗子吧,有这么一段话,我需要匹配URL地址:

The URL is http://www.yonyouhr.com,to connect securely use https://www.yonyouhr.com instead

那么我的正则表达式就可以这么写:

/https?:\/\/[\w.]+/

这样匹配出来的URL地址就是这两个。

指定匹配次数

前面已经提到了可以匹配至少一次,零次或多次,零次或一次,但是就是没有指定次数。想都不用想其实,肯定有这种模式,那就是我们的{}这位同学所擅长的事情了。
直接看栗子:

  • 为重复匹配次数设定一个精确的值 {3}代表字符或字符集合匹配三次
  • 为重复匹配次数设定一个区间 。{1,3}代表字符或字符集合至少一次至多3次
  • 匹配"至少重复多少次" 。{3,}代表字符或字符集合至少重复3次。

防止过度匹配

?只能匹配零个或一个字符,{n},{m,n}也是有一个重复次数上限,而上文其他介绍的都是没有匹配上限的,这样往往会导致匹配过度的现象。例如下面这段话。

This offer is not available to customers living in <B>Hello</B> and <B> World</B>.

如果我要匹配两个<B>标签和里面的内容。如果正则这样写

<[bB]>.*</[bB]>

那么匹配的结果是

<B>Hello</B> and <B> World</B>

一部分组成。
这就是贪婪匹配也就是没有上限的匹配。由此我们需要懒惰型匹配

贪婪元字符 懒惰型元字符
* *?
+ +?
{n,} {n,}?

由此我们改写我们的正则表达式

<[bB]>.*?</[bB]>

得到的结果如下:

<B>Hello</B>   <B> World</B>

两部分组成。

暂时就写这些,下次介绍一下位置匹配和子表达式等

位置匹配

\b:boundary。 简单的说\b匹配的是一个这样的位置,这个位置位于一个能够用来构成单词的字符(字母、数字和下划线,也就是\w相匹配的字符)和一个不能用来构成单词的字符(也就是跟\W相匹配的字符)之间。需要注意一点的是\b只匹配位置,不匹配字符也就是不占用匹配出来的字符的长度。\bcat\b 匹配出来的字符长度就是3而不是5.

\B同理,\B则是匹配一个不是单词边界的连字符。
举个栗子吧:

var a1 = 'Please enter the nine-digit id as it appears on your color - coded pass-key';

我现在要匹配-,仔细的同学已经发现这三个-可以分成两类,第一类是直接与\w仅仅相邻,如pass-key,暂记作类型1吧,另一种是不与其相邻color - coded以空格相邻,类型2。
假如我要匹配类型2,那么正则表达式就应该这样写:

/\B-\B/g

匹配类型1的-,应该这样写:

/\b-\b/g

大家可以在console里面粘贴测试效果,代码如下:

var a1 = 'Please enter the nine-digit id as it appears on your color - coded pass-key';
a1.match(/\B-\B/g);
a1.match(/\b-\b/g);

子表达式

子表达式是一个更大的表达式的一部分;把一个表达式划分为一系列子表达式的目的是为了把那些子表达式当作一个独立元素来使用。子表达式必须用()括起来。

基本用法

栗子来一颗:
比如有这么一段话

Pinging hog.forta.com  [12.159.46.200] with 32 bytes of data.

如果我要匹配上面的ip地址。没学子表达式之前,我想大部分同学会使用以下正则表达式:

\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}

这个模式\d{1,3}重复了4次,他们分别匹配IP地址里的一组数字。IP里的4组数字由.分隔,该字符由模式里的转义序列\.负责匹配。而\d{1,3}\.重复了3次。那么我们可以将前3个化为一类,最后一组单独匹配。正则如下:

(\d{1,3}\.){3}\d{1,3}

子表达式嵌套

子表达式允许嵌套。事实上,子表达式允许多重嵌套,这种嵌套的层次在理论上没有限制,但在实际工作中还是应该遵循适可而止的原则。
栗子还是上面那颗,上面我们最终的表达式如下:

(\d{1,3}\.){3}\d{1,3}

这样写虽然可以匹配到上面的IP地址,但是可以发现,这也就仅仅能匹配到上面的IP,不能确定你匹配到的是否为真实合法的IP。所以我们需要将其限制在0-255这个范围。
合法IP需要具备以下几点要求:

  • 任何一个1位或2位数字
  • 任何一个以1开头的3位数字
  • 任何一个以2开头、第2位数字在0-4之间的3位数字
  • 任何一个以25开头、第3位数字在0-5之间的3位数字

那么正则表达式可以采用如下写法:

(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))

上面这个栗子的正则表达式看起来很难理解。把他们弄明白的关键是把他们分解开、每次只分析和理解一个子表达式。分析各个表达式的原则是先内后外

总结一下 子表达式的作用是把同一个表达式的各个相关部分组合在一起。子表达式必须用()来定义。子表达式的常见用途:对重复次数元字符的作用对象做出精确的设定和控制、对|操作符的OR条件做出准确的定义等。如有必要,子表达式还允许嵌套使用

前后查找

因为JavaScript不支持向后查找,所以本节重点介绍向前查找。

向前查找

向前查找制定了一个必须匹配但不在结果中返回的模式。向前查找实际就是一个子表达式,而且从格式上也确实如此。从语法上看,一个向前查找模式其实就是一个以?=开头的子表达式,需要匹配的文本跟在=的后面。

提示 有些正则表达式文档使用术语"消费"来表述匹配和返回文本的含义。在向前查找里,被匹配的文本不包含在最终返回的匹配结果里面,这被称为不消费

栗子一颗:
比如我要查找下面文本中所有的协议名称

http://www.yonyouhr.com
https://www.yonyouhr.com
ftp://123.109.12.12

那么正则表达式就应该类似这样写:

/(\w+(?=:))/g

这样匹配出来的就是不带有:的协议文本。

俗话说的好,凡事都有两性性,既然有了=那自然也会有!
举个栗子:
我们要匹配下面这个语句中的“<”后面不是“br>”的“<”:

<div>line1</div> <br>   

那么正则表达式就该这样写:

/<(?!br>)/  

OK 个人感觉 这些东西 都弄明白了基本工作就 没啥干不了的了。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions