通俗理解Javascript正则表达式

我们知道,正则表达式是一个处理字符串中很实用的技巧。然而,即便是Javascript写的很厉害的程序猿,有时也会忘掉正则表达式的语法,从而使用起来有些棘手。本文将通俗讲解Javascript中正则表达式的使用,希望对各位有所帮助。
后来又写了一篇关于es6对正则表达式的扩展,查看文章请点击这里
这里感谢下慕课网Samaritan89老师,本文在他视频的基础上进行补充和概括,查看原视频请点击这里

1 一些基本的概念

正则表达式:使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。简单来说,就是按照某种规则去匹配符合条件的字符串。
平时开发过程中,你也许会见到形如这样的正则表达式^([a-zA-Z0-9])+@([a-zA-Z0-9_-])+((\.[a-zA-Z0-9_-]{2,3}){1,2})$,(@﹏@)~(写这行正则的人怎么不去死),那么有没有一种更好的图形化展示的界面能够帮我们分析正则表达式呢,这里推荐一个网址regexper。我们只需要在红框部分输入正则表达式,点击display,下面就会以图形化的方式帮我们分析这个正则。

这样看起来是不是清楚了很多呢?这是一个匹配邮箱的字符串。

2 RegExp对象

Javascript通过内置对象RegExp支持正则表达式,有两种方法实例化RegExp对象。

  • 字面量
  • 构造函数

下面我们通过一个具体例子来演示如何实例化RegExp对象。这里匹配字符串中小写的is变为大写的is。我们先看字面量方法。

var reg = /\bis\b/g;//\b表示单词边界,表示这里匹配的is是一个完整的单词,而不是this中的is,\g表示全文搜索匹配
"He is a boy. This is a dog. Where is she?".replace(reg, "IS");//控制台输出"He IS a boy. This IS a dog. Where IS she?"

接着看下使用构造函数的方式。

var reg = new RegExp("\\bis\\b", "g");//\\b表示转义\b
"He is a boy. This is a dog. Where is she?".replace(reg, "IS");//控制台输出"He IS a boy. This IS a dog. Where IS she?"

这里用到了g这个修饰符,除此以外还有修饰符i。i表示忽略大小写进行匹配,默认大小写敏感。下面看这个例子。

"He is a boy. Is he?".replace(/\bis\b/gi, "0");//控制台输出"He 0 a boy. 0 he?"

可以看到不光小写的is,大写的Is也被替换成了"0"。还有一个修饰符m,m表示进行多行搜索匹配。下面看这个例子。

"He is a boy,\n but she is a girl.".replace(/\bis\b/gm, "0");//控制台输出"He 0 a boy,换行一下
 but she 0 a girl."

这里我们在总结一下修饰符的含义。

修饰符 含义
g 表示全文搜索匹配,不添加,搜索到第一个匹配停止
i 表示忽略大小写进行匹配,默认大小写敏感
m 表示进行多行搜索匹配

3 元字符

正则表达式由两种基本字符类型组成:

  • 原义文本字符,如"abc", "hello"
  • 元字符,如\b

元字符是在正则表达式中有特殊含义的非字母字符,其中包括* + ? $ ^ . | \ ( ) { } [ ]

字符 含义
\t 水平制表符
\v 垂直制表符
\n 换行符
\r 回车符
\0 空字符
\f 换页符
\cX 与X对应的控制字符(Ctrl+X)
…… ……

4 字符类

我们可以使用元字符[ ]来构建一个简单的类。所谓类是指符合某些特性的对象,一个泛指,而不是特指某个字符。表达式[abc]把字符a或b或c归为一类,表达式可以匹配这类的字符。


下面看个例子,把字符串中的a或者b或者c替换成X。

"a1b2c3d4".replace(/[abc]/g, "X");//控制台输出"X1X2X3d4"

我们可以使用^创建反向类,反向类的意思是不属于某类的内容。表达式[^abc]表示不是字符a或b或c的内容。


还是上个例子,我们把[abc]换成[^abc]试试看。

"a1b2c3d4".replace(/[^abc]/g, "X");//控制台输出"aXbXcXXX"

5 范围类

我们可以使用[a-z]来连接两个字符表示从a到z的任意字符,这是个闭区间,包含a,z本身。


下面看个例子,把所有a-z的字母替换成Q。

"a1b2c3d4z9".replace(/[a-z]/g, "Q");//控制台输出"Q1Q2Q3Q4Q9"

[ ]组成的类内部是可以连写的[a-zA-Z]


下面看个例子,把所有的包括大小写的a-z的字母替换成Q。

"a1b2c3d4z9ASDFG".replace(/[a-zA-Z]/g, "Q");//控制台输出"Q1Q2Q3Q4Q9QQQQQ"

6 预定义类及边界

正则表达式提供预定义类来匹配常见的字符串。

字符 等价类 含义
. [^\r\n] 除了回车符和换行符之外的所有字符
\d [0-9] 数字字符
\D [^0-9] 非数字字符
\s [\t\n\x0B\f\r] 空白符
\S ^\t\n\x0B\f\r 非空白符
\w a-zA-Z_0-9 单词字符(字母、数字、下划线)
\W ^a-zA-Z_0-9 非单词字符

下面看个例子,匹配一个ab+数字+任意字符的字符串。
如果是之前的思路,大概要这么写ab[0-9][^\r\n]。现在呢,ab\d.这么写就可以了。


正则表达式还提供了几个常用的边界匹配字符。如前面见到的\b

字符 含义
^ 以xxx开始
$ 以xxx结束
\b 单词边界
\B 非单词边界

下面看个例子,分别将以@+任意字符开头的和以任意字符+@结尾的字符串替换成Q。

"@123@abc@".replace(/^@./g, "Q");//控制台输出"Q23@abc@"
"@123@abc@".replace(/.@$/g, "Q");//控制台输出"@123@abQ"

7 量词

我们希望匹配一个连续出现20次数字的字符串,原来我们可能会这样写\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d,这样会非常麻烦。量词因此出现了。

字符 含义
? 出现0次或1次(最多出现1次)
+ 出现1次或多次(至少出现1次)
* 出现0次或多次(任意次)
{n} 出现n次
{n,m} 出现n到m次
{n,} 至少出现n次

注意:{n,m},逗号后面没有空格!!!
\d?的图形化展示:


\d+的图形化展示:

\d*的图形化展示:

\d{2}的图形化展示:

\d{2,5}的图形化展示:

那么你会发现,怎么没有表示最多出现n次的量词呢?我要想表示最多出现5次应该怎么写呢?
很简单,最多出现5次,就是可能出现0,1,2,3,4,5次,即\d{0,5}

原来匹配一个连续出现20次数字的字符串,就可以写成\d{20}
下面看一个复杂一点的,\d{20}\w\d?\w+\d*\d{3}\w{3,5}\d{3,}表示什么意思呢?
我们先慢慢分析,一开始\d{20}表示出现20个连续的数字,然后\w表示出现1个单词字符,然后\d?表示紧跟着出现最多出现1次的数字,然后\w+表示出现至少1次的单词字符,然后\d*表示出现任意次的数字,然后\d{3}表示出现3个连续的数字,然后\w{3,5}表示出现3-5次单词字符,最后\d{3,}表示至少再出现3个连续的数字。我们用图形化界面展示下:

8 正则贪婪模式与非贪婪模式

有这样一个字符串"12345678",对其进行这样的正则匹配\d{3,6},究竟会匹配多少个呢?是3个,4个,5个,还是6个呢?

"12345678".replace(/\d{3,6}/g, "X");//控制台输出"X78"

可以看到,正则表达式会尽可能多的去匹配,这就是正则的贪婪模式。
如果我们只想让它匹配3个,怎么做呢?这就是正则的非贪婪模式,做法很简单,在量词后加上?即可。

"12345678".match(/\d{3,6}?/g, "X");//控制台输出"XX78"

9 分组

我们要匹配字符串Byron连续出现3次的场景,之前我们可能会这样写ByronByronByron,这样写灰常麻烦。有人说,我学了量词,我知道也可以这样写Byron{3},但是这样写真的对吗?用图形化界面展示下:


可以看到,这里匹配的是Byronnn这样的字符串,前面并没有重复匹配。如何把前面的内容也重复匹配到呢?这里用到了分组( )。上面的可以写成这样(Byron){3}

下面看个例子,我们想把所有小写字母+数字重复出现3次的场景替换成X。

"a1b2c3d4".replace(/([a-z]\d){3}/g, "X");//控制台输出"Xd4"

我们如果想表示或关系,就要用到|。下面看个例子,我们想把Byronsper或者ByrCasper的字符串替换成X。

"ByronsperByrCasper".replace(/Byr(on|Ca)sper/g, "X");//控制台输出"XX"

在处理时间日期的时候,经常需要我们把2017-12-25替换成12/25/2017,这时候应该怎么处理呢?这里引入反向引用,通过$1-$n来捕获第1-n个分组。


"2017-12-25".replace(/(\d{4})-(\d{2})-(\d{2})/g, "$2/$3/$1");控制台输出"12/25/2017"

如果不希望捕获某些分组,只需要在分组内加上?:就可以了。如(?:Byron).(ok),其图形化界面如下:

10 前瞻

前瞻就是在正则表达式匹配到规则的时候,向前检查是否符合断言,后顾方向相反。Javascript不支持后顾。

名称 正则 含义
正向前瞻 exp(?=assert)
负向前瞻 exp(?!assert)
正向后顾 exp(?<=assert) Javascript不支持
负向后顾 exp(?<!assert) Javascript不支持

我们相匹配一个单词字符且后面是数字的场景,\w(?=\d)

"a2*34v8".replace(/\w(?=\d)/g, "X");//控制台输出"X2*X4X8"

11 字符串对象方法

search()方法用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。方法返回第一个匹配结果的index,查找不到返回-1。search()方法不执行全局匹配,并且总是从字符串的开始进行检索。

"a1b2c3d1".search("1");//控制台输出1
"a1b2c3d1".search(1);//控制台输出1
"a1b2c3d1".search(/1/g);//控制台输出1

match()方法将检索字符串,以找到一个或多个与正则表达式相匹配的文本。正则表达式是否具有标志g对结果影响很大。
没有标志g,那么match()方法就只能在字符串中执行一次匹配,如果没有找到任何匹配的文本,将返回null,否则它将返回一个数组,其中存放了与它找到的匹配文本有关的信息。
有标志g,那么match()方法将执行全局检索,找到字符串中的所有匹配子字符串。
split()方法可以将字符串分割为字符数组。

"a1b2c3d4e".split(/\d/g);控制台输出["a", "b", "c", "d", "e"]

replace()方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
替换的内容可以使一个函数function,function会在每次匹配替换的时候调用,有4个参数

  • 匹配字符串
  • 正则表达式分组内容,没有分组则没有该参数
  • 匹配项在字符串中的index
  • 原字符串
"a1b2c3d4e5".replace(/\d/g, function(match, index, origin){
    console.log(index);//控制台打印1 3 5 7 9
    return parseInt(match) + 1;//控制台打印"a2b3c4d5e6"
});
"a1b2c3d4e5".replace(/(\d)(\w)(\d)/g, function(match, group1, group2, group3, index, origin){
    console.log(match);//控制台打印1b2 3d4
    return group1 + group3;//控制台打印"a12c34e5"
});

12 几道最近遇到的题(补充)

看一道leetcode上的题,原题链接请点击这里
500. Keyboard Row
Given a List of words, return the words that can be typed using letters of alphabet on only one row's of American keyboard like the image below.


Example 1:

Input: ["Hello", "Alaska", "Dad", "Peace"]
Output: ["Alaska", "Dad"]

Note:
1.You may use one character in the keyboard more than once.
2.You may assume the input string will only contain letters of alphabet.
翻译过来基本意思就是,给你一个字符串数组,你判断下每个数组元素出现的字母是不是全都是键盘某一行的,如果是就返回这个数组元素。这道题解法有很多,这里着重解释下用Javascript正则表达式解答本题。下面附上源码。

/**
 * 2017.5.3 9:07
 * @param {string[]} words
 * @return {string[]}
 */
var findWords = function(words) {
    var len = words.length,i,str = new Array();
    for(i=0;i<len;i++){
        if(/^[qwertyuiop]*$/i.test(words[i]) || /^[asdfghjkl]*$/i.test(words[i]) || /^[zxcvbnm]*$/i.test(words[i])){//test() 方法用于检测一个字符串是否匹配某个模式.
            str.push(words[i]);
        }
    }
    return str;
};

这里的/^[asdfghjkl]*$/i相当于是以asdfghjkl中的任一字母开头,重复任意次,再以任一字母结尾。


再看一道leetcode上的题,突然发现用正则表达式去做字符串匹配的题真的好方便,原题链接点击这里
** 520. Detect Capital **
Given a word, you need to judge whether the usage of capitals in it is right or not.
We define the usage of capitals in a word to be right when one of the following cases holds:
1.All letters in this word are capitals, like "USA".
2.All letters in this word are not capitals, like "leetcode".
3.Only the first letter in this word is capital if it has more than one letter, like "Google".
Otherwise, we define that this word doesn't use capitals in a right way.
** Example 1: **

Input: "USA"
Output: True

** Example 2: **

Input: "FlaG"
Output: False

** Note: **
The input will be a non-empty word consisting of uppercase and lowercase latin letters.
翻译过来的意思就是判断一个字符串是否符合书写规范,这里给出三条规则:1.全大写;2.全小写;3.第一个字母大写剩下的小写。如果满足以上三条规范则返回true,否则返回false。这里不再对此正则表达式进行详细解释,请参考上面教程。下面附上源码。

/**
 * 2017.5.4 11:26
 * @param {string} word
 * @return {boolean}
 */
var detectCapitalUse = function(word) {
    return (/^[A-Z][a-z]*$/.test(word) || /^[A-Z]*$/.test(word) || /^[a-z]*$/.test(word));
};

13 总结

以上是听过慕课网Javascript正则表达式视频课后的一些笔记,希望对各位有所帮助。写的不对的地方还请各位大神提出意见,我会加以改正。码字不易,请尊重作者版权,转载注明出处。
By BeLLESS 2017.4.8 17:57

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容