正则表达式学习笔记(转)

前言

转载自:https://juejin.im/post/582dfcfda22b9d006b726d11,有修改。

学习目标

  • 了解正则表达式语法
  • 在IDE中使用正则表达式
  • 在javascript中使用正则表达式处理字符串

什么是正则表达式

正则表达式,又称正规表达式、正规表示法、正规表达式、规则表达式、常规表示法(英文:Regular Expression,在代码中常简称regexregexpRE),计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些匹配某个模式的文本。

通俗地讲就是按照某种规则去匹配符合条件的字符串

利用图形化工具理解正则表达式

有一个辅助理解正则表达式的在线工具(regexper.com),可以自动将输入的正则表达式转换为铁路图(又叫语法图syntax diagrams)下面是一些例子。

手机号正则

/^1[34578][0-9]{9}$/

以1开头,第二位为3 4 5 7 9中某一个,以9为(本省1次加回转8次)0-9数字结尾。

单词边界

/\bis\b/

is前后都是单词的边界,下面的实验有助于理解。

var reg1 = /\bis\b/g,
reg2 = /is/g,
str =  'this is a fish';
console.log(str.replace(reg1, 'X'));    //this X a fish
console.log(str.replace(reg2, 'X'));    //thX X a fXh

URL分组替换

/http:(\/\/.+\.jpg)/

正则表达式中括号用来分组,这个时候我们可以通过用$1来获取group#1的内容。

var reg3 = /http:(\/\/.+\.jpg)/;
console.log('http://img.host.com.abc.jpg'.replace(reg3, '$1')); 
// //img.host.com.abc.jpg

说下这个正则的意义,如果网站用了https,网站引用静态资源也必须是https,否则报错。如果写成//会自动识别http或者https

日期匹配与分组替换

/^\d{4}[/-]\d{2}[/-]\d{2}$/

  • Start of line是由^生效,表示以此开头
  • 对应结尾End of line$生效,表示以此结尾
  • 接着看digit\d生效,表示数字
  • 3 times{4}生效表示重复4次,铁路图中实际是3+1
  • one of表示在给出的所有可选项中选择一个

下面的代码使用它来验证日期字符串。

var reg4 = /^\d{4}[/-]\d{2}[/-]\d{2}$/;
console.log(reg4.test('2018-2-7')); // false
console.log(reg4.test('2018-02-07'));   // true
console.log(reg4.test('2018/02/07'));   // true

结合URL分组替换所用到的分组特性,我们可以轻松写出日期格式化的方法,改造后的正则表达式如下。

/^(\d{4})[/-](\d{2})[/-](\d{2})$/

轻松的拿到各个分组的内容后,就可以替换成我们期望的格式。

var reg5 = /^(\d{4})[/-](\d{2})[/-](\d{2})$/,
    dateStr = '2018/02/07';
console.log(dateStr.replace(reg5, '$1年$2月$3日'));    
//"2018年02月07日"
console.log(dateStr.replace(reg5, '$1-$2-$3')); 
//"2018-02-07-"
console.log(dateStr.replace(reg5, '$2月'));  
//"02月"

到现在已经能结合图形化工具看懂正则表达式了,如果有自己写,还需要进一步深入。

JavaScript中的RegExp对象

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

字面量方法

const reg = /\bis\b/g

构造函数

const reg = new RegExp('\\bis\\b', 'g');

注意:构造函数第一个参数为正则表达式字符串,需要双重转义,第二个参数为修饰符,修饰符g代表全局搜索。

正则表达式语法

修饰符(g i m

修饰符与其他部分语法不同,字面量方法声明的时候放到//后面,构造函数声明的时候,作为第二个参数传入。整个正则表达式可以理解为正则表达式规则字符串+修饰符。

  • g:global,执行一个全局匹配
  • i:ignore case,执行一个不区分大小写的匹配
  • m: multiple lines,多行匹配

可以同时使用多个修饰符。

g修饰符详例

var reg6 = /is\b/,
    reg7 = /is\b/g,
    str = 'this is a sentence contaning many is.';
console.log(str.replace(reg6, 'O'));
//thO is a sentence contaning many is.
console.log(str.replace(reg7, 'O'));
//thO O a sentence contaning many O.

可以看到添加了g修饰符,所有模式匹配的字符串都替换了,而不添加的话,只会替换第一处。

i修饰符详例

var reg8 = /is\b/g,
    reg9 = /is\b/gi,
    str = 'Is this a kiss?';
console.log(str.replace(reg8, 'O'));
//Is thO a kiss ?
console.log(str.replace(reg9, 'O'));
//O thO a kiss ?

i时是忽略大小写的。

元字符

正则表达式字面量中字符可分为原义字符、非打印字符、元字符,其中元字符在正则表达式中具有一种或多种特殊用途,如果要匹配它们,需要将它们转义,这些元字符包括( { [ * + ? $ ^ . | \ ) } ]

原义字符

比如/is\b/中的is

\将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,n匹配字符n\n匹配换行符。序列\\匹配\,而\(匹配(

非打印字符

比如换页符\f、换行符\n、制表符\t等等,以\n为例。

var reg10 = /\n/,
    str = 'sentence 1. \n sentence 2.';
console.log(str.replace(reg10, 'O'));
//sentence 1. O sentence 2.

其他非打印字符在前端用得少,通常在后端处理文本文件时才会用到。

字符类[]

前面的手机号的例子(/^1[34578][0-9]{9}$/)中,我们使用过[],其中的[34578]表示34578中的任意一个数字即可。在日期匹配与分组替换的例子(/^\d{4}[\-]\d{2}[\-]\d{2}$/)中,[\-]表示\-任选一个。

var reg11 = /[abc]/g,
    str = 'a 1 b 2 c 3 d 4';
console.log(str.replace(reg11, 'O'));
//O 1 O 2 O 3 d 4

字符类取反[^]

表示给出的所有字符之外的任意字符。

var reg12 = /[^abc]/g,
    str = 'a 1 b 2 c 3 d 4';
console.log(str.replace(reg12, 'O'));
//aOOObOOOcOOOOOO

范围类[-]

正则表达式支持一定范围规则,比如[a-z][A-Z][0-9],也可以组合起来如[a-z0-9],如果字符-本身也是可选项,加在最后即可。

var reg13 = /[a-zA-C1-3]/g,
    reg14 = /[0-9-]/g,
    str1 = 'a A 1 b B 2 x X 9',
    str2 = '2016-11-11';
console.log(str1.replace(reg13, 'O'));
//O O O O O O O X 9
console.log(str2.replace(reg14, 'O'));
//OOOOOOOOOO

预定义类

为了编程方便,JavaScript中具有一些预定义类可以直接使用。

字符 等价类 含义
. [^\n\r] 除了回车符和换行符之外的所有字符
\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\d./

边界

字符 含义
^ 字符串开头
$ 字符串结尾
\b 单词边界,指[^a-zA-Z_0-9]
\B 非单词边界

边界指定义匹配的边界条件,上面基本都在例子中碰到了,下面演示下\b\B

var reg15 = /\bdog/g,
    reg16 = /\Bdog/g,
    str = '@dog@dogdog@';
console.log(str.replace(reg15, 'X')); 
//@X@Xdog@
console.log(str.replace(reg16, 'X'));
//@dog@dogX@

量词

字符 含义
? 出现0或1次
* 出现0,1,2…次(任意次)
+ 出现1,2,3…次(正数次)
{n} 正好出现n次
{m,n} 出现m到n次(含两端)
{n,} 至少出现n次

这个正则表达式使用了上面的各种形式的量词:/\d?@\d*@\d{10}@\d{10,20}@\d{10,}/

贪婪与(惰性)非贪婪

正则表达式默认会匹配贪婪模式,什么是贪婪模式呢?如其名,在符合规则的情况下尽可能多地匹配字符。如下面这个例子。

var reg17 = /\d{3,6}/;
console.log('1234567890'.replace(reg17, 'X'));
//X7890

贪婪模式下,匹配了最多的情况。

与贪婪模式对应的就是惰性模式,此时尽可能少地匹配字符。如何开启懒惰模式呢?在量词后面加?。继续上面的例子:

var reg18 = /\d{3,6}?/;
console.log('1234567890'.replace(reg18, 'X'));
//X4567890

更深入的介绍可参看进阶正则表达式

分组与反向引用

分组,又称为子表达式。把正则表达式拆分成小表达式。来看例子:

不分组

/abc{2}/

量词仅作用到最后的c

分组

/(abc){2}/

注意这里的group#1

分组虽然和运算符()很像,但是分组在正则表达式中,需要着重理解其含义。比如如/^(http|https)/这样滥用分组没有必要,完全可以写作/^https?/。你写的正则特别长的时候,会出现一堆没用的结果。

分组往往和反向引用一起使用。具体来说,当一个正则表达式被分组以后,每个分组自动被赋予一个组号,从左到右是$1 $2 …

在把之前的例子拿出来:/^(\d{4})[/-](\d{2})[/-](\d{2})$/

可以很容易拿到分组group#1group#2group#3对应的匹配内容,分别是$1$2$3

var reg19 = /^(\d{4})[/-](\d{2})[/-](\d{2})$/,
    str = '2018/02/10';
console.log(str.replace(reg19, '$1年$2月$3日'));
//2018年02月10日
console.log(str.replace(reg19, '$1-$2-$3'));
//2018年02月10日
console.log(str.replace(reg19, '$2月'));
//02月

如果一个分组不想捕获,可以在括号内起始位置加上?:,如:/^(?:\d{4})[/-](\d{2})[/-](\d{2})$/

前瞻(进阶,选看)

正则表达式中有前瞻(Lookahead)和后顾(Lookbehind)的概念,这两个术语非常形象地描述了正则引擎的匹配行为。需要注意一点,正则表达式中的前和后和我们通常所理解的前后有所不同。一段文本,我们一般习惯把文本开头的方向称作前面,文本末尾方向称作后面。然而对于正则表达式引擎来说,因为它是从文本头部向尾部开始解析的(可以通过正则选项控制解析方向),因此对于文本尾部方向正则引擎还没走到那块,称作,而文本起始位置已经走过了,称作。需要注意的是,后顾性能损耗比较大,js只支持前瞻。按我个人的理解,前瞻的内容并不属于我们的匹配目标,而是我们在匹配目标时,需要向前查看的环境,这种环境要求就是前瞻。

前瞻分两种,正向前瞻(?=xxx)和负向前瞻(?!xxx)。下面是例子。

var reg20 = /[a-z]{2}(?=\d{2})/g,
    reg21 = /[a-z]{2}(?!\d{2})/g,
    str = 'aa11bb22cc**';
console.log(str.replace(reg20, 'X'));
//X11X22cc**
console.log(str.replace(reg21, 'X'));
//aa11bb22X**

知识汇总应用

题目

将字符串123456789转换从低位起每三位用逗号区隔一次的货币形式,即123,456,789

参考

'123456789'.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')

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

推荐阅读更多精彩内容