让你彻底领悟正则表达式

什么是正则表达式

正则是一个用来处理字符串的规则

  1. 正则只能用来处理字符串
  2. 处理一些包含两个方面:
    ① 验证当前字符串是否符合某个规则 正则匹配
    ② 把一个字符串中符合规则的字符获取到 正则捕获

正则表达式的创建

let reg1 = /^\d+$/g; //=>字面量创建
let reg2 = new RegExp("^\\d+$","g"); //=>构造函数方式

元字符

正则两个斜杠之间包起来的都是“元字符” ,斜杠后面出现的都是“修饰符”

运算符 描述
\ 转移符(在一个普通字符转义为特殊的字符,例如:\d,把有特殊含义转换为普通意思,例如:\. 此处的点就不是任意字符,而是一个小数点)
^ 以什么开头
$ 以什么结尾
. 除了 \n 以外的任意字符
\n 匹配一个换行符
( ) 正则分组,把一个大正分为几个小正则
( ?: ) 正则分组,只匹配不捕获
( ?= ) 正向预查
( ?! ) 负向预查
x | y x或者y中的其中一个
[xyz] x或者y或者z中的其中一个
[^xyz] 除了这三个以外的任意一个字符
[a-z] a-z之间的任意一个字符
[^a-z] 除了a-z以外的任意字符
\d 0-9之间的一个数字
\D 除了0-9之间的任意字符
\s 匹配一个空白字符、空格、制表符、换页符…
\S 匹配任何非空白字符
\b 匹配一个边界符,'ni'(z左边和i右边是边界)'ni-hao'(z左边i右边,h左边o右边是边界)
\B 匹配非边界符
\w 匹配 数字、字母、下划线 中的任意一个 等价于 [0-9a-zA-Z]

普通元字符

只要在正则中出现的元字符(在基于字面量方式创建),除了特殊和有量词意义的以外,其余的都是普通元字符

量词

运算符 描述
* 出现零到多次
出现零到一次
+ 出现一到多次
{ n } 出现n次
{ n, } 出现n到多次
{ n, m } 出现n到m次

修饰符

运算符 描述
i 忽略大小写
m 表示多行模式
g 匹配全局变量

正则表达式中的几个规则:

中括号的一些细节

  1. 中括号出现的元字符一般都是代表本身的含义
  2. 中括号中出现的两位数,不是两位数,而是两个数字中的任意一个

代码实例

let reg = /^.$/ //=>一个正则设置了^和$,那么代表的含义其实就是只有xxx
console.log(reg.test('n')); //true
console.log(reg.test('1')); //true

let reg = /^[.]$/ //=>中括号里面的元字符代表本身
console.log(reg.test('n')); //false
console.log(reg.test('1')); //false
console.log(reg.test('.')); //true

let reg = /^[18]$/ //=>中括号里面的两位数代表两个数
console.log(reg.test('18')); //false
console.log(reg.test('1'));  //true
console.log(reg.test('8'));  //true

let reg = /^[12-65]$/ // 这个正则的意思1或者2-6或者5
console.log(reg.test('13'));  //false 不是12~65
console.log(reg.test('6'));   //true

//年龄18-65岁之间的数
18~19 1[89]
20-59 [2-5]\d
60-65 6[0-5]
let reg = /^((1[8-9])|([2-5]\d)|(6[0-5]))$/;

//匹配[object aaa]
let reg = /^\[object .+\]$/;

小括号分组作用

  1. 改变默认优先级
  2. 分组捕获
  3. 分组引用

第一、改变默认优先级

let reg = /^18|19$/
console.log(reg.test('18'));  //true
console.log(reg.test('19'));  //true
console.log(reg.test('1819'));//true
console.log(reg.test('189')); //true
console.log(reg.test('181')); //true
console.log(reg.test('819')); //true
console.log(reg.test('119')); //true

let reg = /^(18|19)$/
console.log(reg.test('18'));  //true
console.log(reg.test('19'));  //true
console.log(reg.test('1819'));//false
console.log(reg.test('189')); //false
console.log(reg.test('181')); //false
console.log(reg.test('819')); //false
console.log(reg.test('119')); //false

第二、分组引用

let reg = /^([a-z])([a-z])\2\1$/; //=>正则中出现的\1代表和第一分组出现一模一样的内容...
console.log(reg.test('oppo'));  //true
console.log(reg.test('poop'));  //true

第三、分组捕获

// 编写一个正则匹配身份证号码
let reg = /^\d{17}(\d|X)$/; //简单:只匹配知否符合格式,不能提取身份证中的一些信息 
let reg = /^\(d{6})(d{4})(d{2})(d{2})(d{2})(\d)(?:\d|X)$/
console.log(reg.exec('130987200198983789'));  //实现正则捕获

问号在正则中的作用

  1. 量词元字符:出现零次或者一次 /-?/- 出现一次或者不出现
  2. 取消贪婪性:/\d+?/ 捕获时候只捕获最短匹配的内容
  3. 只匹配不捕获:?:
  4. 正向预查:?=
  5. 负向预查:?!

正则捕获

把一个字符串中和正则匹配的部分捕获到

正则方法

exec(正则捕获)

  1. 实现的是正则捕获,获取的结果是一个数组,如果不匹配获取的结果是 null
  2. 如果想获取大正则中部分信息,我们可以把这部分使用小括号包起来,形成一个分组,这样在捕获的时候,不仅可以把大正则匹配内容捕获到,而且每一个小分组中的内容也捕获到了(分组捕获)。
  3. 有时写小分组不是为了捕获信息,只是为了改变优先级或者进行分组引用,此时可以在分组的前面加上 ?: ,代表只匹配,不捕获。

test(字符串验证):验证字符串是否符合某个规格的正则

基于exec 可以实现正则的捕获

  • 如果当前正则和字符串不匹配,捕获的结果是 null
    • 如果匹配,捕获的结果是一个数组
      0:大正则捕获的内容
      index:正则捕获的起始索引
      input:原始操作的字符串
    • 执行一次exec只能捕获到第一个和正则匹配的内容(正则的懒惰性)

代码实例

let str = "nihao2018nihao2019";
let reg = /\d+/;
console.log(reg.exec(str));

基于test匹配机制

基于test 匹配的时候,如果设置了g test 匹配也相当于捕获,修改了 lastIndex

代码实例

let str = "nihao2018nihao2019";
let reg = /\d+/g; 
console.log(reg.test(str)); //=>true
console.log(reg.lastIndex); //=>6
console.log(reg.exec(str)); //=>2019

正则的懒惰性和贪婪性

正则懒惰性

每一次捕获的时候,只会捕获符合正则的第一项,后面符合正则规则的也不会再继续捕获 。
解决正则捕获懒惰性,我们需要加全局修饰符 g

代码实例

let str = "nihao2018nihao2019";
let reg = /\d+/g;
console.log(reg.lastIndex) //0
console.log(reg.exec(str));//['2018']
console.log(reg.lastIndex) //9
console.log(reg.exec(str));['2019']
console.log(reg.lastIndex) //18
console.log(reg.exec(str));//null
console.log(reg.lastIndex) //0
console.log(reg.exec(str));//['2018']

正则贪婪性

每一次捕获的时候,总是捕获到和正则匹配中最长的内容,例如:2 符合 \d+ 2666 也符合 \d+ ,但捕获的是最长的内容 2666
解决方案:在量词元字符后面加 ?

代码实例

let str = "nihao2018nihao2019";
let reg = /\d+?/g; 
console.log(reg.exec(str)); //=>['2']

字符串方法

replace(字符串替换)
字符串replace实现正则捕获的方法(本身是字符串替换)

split(字符串分割)
把符合正则规则的内容分割(去除)成数组

match(字符串捕获)
字符串 match 方法,捕获符合正则规则的内容

正则捕获局限性: 在正则捕获的时候存在分组,捕获的时候不仅仅捕获把大正则匹配到的字符捕获到(数组第一项),而且把小分组匹配的内容也单独抽取出来(数组中的第二项开始就是小分组捕获的内容——分组捕获),而 ?: 是用来阻止分组捕获内容的,“只匹配不捕获”。

第一、match捕获

代码实例

let str = "nihao2018nihao2019";
let reg = /\d+/g;
str.match(reg); //=>["2018", "2019"]

match方法局限性:match捕获的内容只有大正则捕获的,小分组内容没有单独抽取出来。

let str = "nihao{2018}nihao{2019}";
let reg = /\{(\d+)\}/g; 
console.log(reg.exec(str));  //=>["{2018}", "2018", index: 5, input: "nihao{2018}nihao{2019}", groups: undefined]

console.log(str.match(reg)); //=>["{2018}", "{2019}"] match方法也有自己的局限性,在正则设置了g的情况下,基于match捕获的内容只有大正则捕获的,小分组内容没有单独抽取出来。

第二、split捕获

把匹配符合正则规则的字符串分割成数组

代码示例

let str = '2019/5/20 18:30:00',
    ary = str.split(/(?:\/| |:)/g); //=> 匹配 "/"、" "、":" 作为分割
console.log(ary); //=>["2019", "5", "20", "18", "30", "00"]

第三、replace捕获

replace实现正则捕获的方法(本身是字符串替换)

代码实例

//将nihao替换成你好
let str = "nihao2018nihao2019";
str.replace(/nihao/g,'你好');

replace 原理

let str = "nihao{val:2018}nihao{val:2019}",
    reg = /\{val:(\d+)\}/g
str.replace(reg,"@"); //=>每一次把当前“大正则”匹配的结果用第二个传递的字符串替换掉
console.log(str); //=>'nihao@nihao@'

str.replace(reg,"$1"); //=>$1不是拿这个字符串替换掉大正则匹配的内容,此处的$1代表第一个分组匹配的内容,等价于:RegExp.$1
console.log(str); //=>'nihao2018nihao2019'

/*
 1. reg 和 str 匹配多少次,函数就触发了多少次,而且传递了一些参数信息值
 2. 每一次arg中存储的信息,和执行exec捕获的信息相似(内置原理:每一次正则匹配到结果,都把函数执行,然后基于exec把本次匹配的信息捕获到,然后把捕获的信息传递个这个函数)
 3. 每一次函数中返回的是什么,就把大正则匹配的内容替换成什么
*/
str.replace(reg,(...rag)=>{
    console.log(arg);
    return 'AA';
});
console.log(str); //=> 'nihaoAAnihaoAA'

综合练习

//事件字符串格式化 "2019/4/30 18:30:23" => "04-30 17:50"
//方法一:获取时间字符串中所有数字(split)
let str = '2019/4/30 18:30:23';
ary = str.split(/(?:\/| |:)/g);
//function fn (time) {return time<10?'0'+time:null;}
let [,month,day,hours,minutes] = ary,
    //result = `fn(${month})-fn(${day}) fn(${hours}):fn(${minutes})`;
    result = `${month}-${day} ${hours}:${minutes}`;
    console.log(result)

//方法二:
/*
  1. 利用match方法获取到时间数组;
  2. 指定最后想要的时间格式,我们基于这个数组中的内容拼接好即可
*/

let str = '2019/4/30 18:30:23';
//map相对于forEach来讲多了返回值,函数中return是什么,就是当做数组迭代的这一项替换成什么。
let ary = str.match(/\d+/g).map(item=>{
    return item<10?'0'+item:item;
});
let timeFormat = {0}年{1}月{2}日 {3}时{4}分{5}秒

timeFormat.replacr(/\{\d\}/g,(...arg)=>{
    let [,index] = arg;
    return ary[index];
});

//在正则原型上扩展一个方法,把符合正则规则的捕获到
RegExp.prototype.myExecAll = function(str) {
    let result = [],
        ExecAry = this.exec(str);
    //为了防止出现死循环:我们检测一下正则是否加g,没有加g只把第一次捕获的返回即可。
    if (!this.global) {
        return this.exec(str);
    }
    while (ExecAry) {
        result.push(ExecAry[0]);
        ExecAry = this.exce(str);
    }
    return result;
}
console.log(reg.myExecAll(str));

//在String原型上增加一个格式化时间格式方法
String.prototype.myTimeFormat = function myTimeFormat(template = '{0}年{1}月{2}日 {3}时{4}分{5}秒') {
    let ary = this.match(/\d+/g).map((item => item < 10 ? "0" + item : item));
    return template.replace(/\{(\d)}/g, (...[, index]) => ary[index] || '00'); // 如果没有则为00代替
}

let str = '2019/4/30 18:30:23';
console.log(str.myTimeFormat('{1}月{2}日')); //04月30日
console.log(str.myTimeFormat('{1}-{2} {3}:{4}')); //04-30 18:30

常用的正则表达式

//匹配身份证号码
let reg = /^\(d{6})(d{4})(d{2})(d{2})(d{2})(\d)(?:\d|X)$/
console.log(reg.exec('130987200198983789'));  //实现正则捕获

//匹配有效数字正则表达式
/*分析规则
    1. 可以出现+/-号:可以没有,也可以有一个
    2. 整数一位或者多位,多位数不能以0开头
    3. 小数部分:可能有可能没有,有小数点后面至少要跟一位数字
*/
let reg = /^[+-]?(\d|([1-9]\d+))(\.\d+)?$/;

//正则手机号码验证
/*分析规则:
    1. 11位数字组成
    2. 以1开头
*/
let reg = /^1\d{10}&/;

//中文汉字
/*分析规则:
    1. 中文汉字[\u4E00-\u9FA5]
*/
let reg = /^[\u4E00-\u9FA5]{2,}(·[\u4E00-\u9FA5]{2,})?&/;


//邮箱
/*分析规则:
    1. xxxxx@xxxx.xx.xx
    2. 第一部分:数字、字母、下划线、"-" 、".",但是"-"和"."不能作为开头,不能连续出现 "-" 或者 "."
*/
let reg = /^\w+([-.]\w+)*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*(\.[A-Za-z0-9]+)&/;

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