目录

  1. RegExp
  2. 组成(两部分组成)
  3. 常用易错元字符剖析
  4. 常用的正则表达式
  5. 正则的捕获
  6. 正则表达式中的分组捕获和分组引用
  7. 正则的贪婪性
  8. 正则的其他捕获方法
  9. 正则中括号的作用

    前言

    RegExp不论是前端还是后端的开发同学都是会使用到。一个好的RegExp能帮你在日常的开发中,提升开发效率。在某些厂的面试中,可能也会遇到关于RegExp的相关问题。今天小编就不完全指北。

1. RegExp

RegExp 全称Regular Expression,作用,用来处理字符串规则。它是一个规则,可以验证字符串是否符合某个规则(test),也可以把字符串中符合规则的内容捕获到(exec/match)。

2. 组成(两部分组成)

1. 元字符

  • 量词元字符:设置出现的次数
    • “?” 代表出现零到一次
    • “*” 代表零到多次
    • “+” 代表一到多次
    • {n} 代表出现n次
    • {n,} 代表n到多次
    • {n,m} 代表出现n到m次
  • 特殊元字符:单个或者组合一起的特殊的含义
    • \ 转义字符
    • . 除\n(换行符)以外的任意字符
    • ^ 以哪一个元字符作为开始
    • $ 以哪一个元字符作为结束
    • \d 0~9之间的一个数字
    • \D 非0~9之间的一个数字
    • \n换行符
    • \w 数字字母下划线的一个任意字符
    • \W 非数字字母下划线的一个任意字符
    • \s 一个空白字符(包含空格、制表符、换页符)
    • \S 非
    • \b 一个单词的边界
    • \t 制表符
    • | x|y中一个字符
    • [xyz] 或者,x或者y或者z
    • [^xy] 非x,y的任意字符
    • [a-z] 指定a-z这个返回中的任意字符
    • [^a-z]
    • () 分组
    • (?:) 只匹配不捕获
    • (?=) 正向预查
    • (?!) 负向预查
  • 普通元字符:代表本身含义

    2. 修饰符

  • i:忽略大小写匹配

  • m:忽略换行匹配
  • g:全局匹配
  • u:含义为“Unicode 模式”,用来正确处理大于\uFFFF的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码。(RegExp.prototype.unicode)
  • y:“粘连”(sticky)修饰符(RegExp.prototype.sticky)。
  • s:在正则中.可以匹配除\n以外的任意字符,修饰符s可以让.匹配所有字符(RegExp.prototype.dotAll)

3. 常用易错元字符剖析

1. ^ $

  1. let reg = /^\d/;
  2. console.log(reg.test("stone")); //=>false
  3. console.log(reg.test("2019stone"));//=>true
  4. console.log(reg.test("stone2019"));//=>false
  1. let reg = /\d$/;
  2. console.log(reg.test("stone")); //=>false
  3. console.log(reg.test("2019stone"));//=>false
  4. console.log(reg.test("stone2019"));//=>true

2. \

  1. //=>.不是小数点,是除\n外的任意字符
  2. let reg = /^2.3$/;
  3. console.log(reg.test("2.3"));//=>true
  4. console.log(reg.test("2@3"));//=>true
  5. console.log(reg.test("23"));//=>false
  6. //=>基于转义字符,让其只能代表小数点
  7. reg = /^2\.3$/;
  8. console.log(reg.test("2.3"));//=>true
  9. console.log(reg.test("2@3"));//=>false
  10. let str = "\\d";
  11. reg = /^\d$/; //=>\d代表0-9的数字
  12. console.log(reg.test(str)); //=>false
  13. reg = /^\\d$/; //=>把特殊符合转换为普通的
  14. console.log(reg.test(str)); //=>true

3. x|y

  1. let reg = /^18|29$/;
  2. console.log(reg.test("18")); //=>true
  3. console.log(reg.test("29")); //=>true
  4. console.log(reg.test("129")); //=>true
  5. console.log(reg.test("189")); //=>true
  6. console.log(reg.test("1829")); //=>true
  7. console.log(reg.test("829")); //=>true
  8. console.log(reg.test("182")); //=>true
  9. //---直接x|y会存在很乱的优先级问题,一般我们写的时候都伴随着小括号进行分组,因为小括号改变处理的优先级 =>小括号:分组
  10. reg = /^(18|29)$/;
  11. console.log(reg.test("18")); //=>true
  12. console.log(reg.test("29")); //=>true
  13. console.log(reg.test("129")); //=>false
  14. console.log(reg.test("189")); //=>false
  15. //=>只能是18或者29中的一个了

4. []

  1. //1.中括号中出现的字符一般都代表本身的含义
  2. let reg = /^[@+]$/;
  3. console.log(reg.test("@")); //=>true
  4. console.log(reg.test("+")); //=>true
  5. console.log(reg.test("@@")); //=>false
  6. console.log(reg.test("@+")); //=>false
  7. reg = /^[\d]$/; //=>\d在中括号中还是0-9
  8. console.log(reg.test("d"));//=>false
  9. console.log(reg.test("\\"));//=>false
  10. console.log(reg.test("9"));//=>true
  11. //2.中括号中不存在多位数
  12. reg = /^[18]$/;
  13. console.log(reg.test("1")); //=>true
  14. console.log(reg.test("8")); //=>true
  15. console.log(reg.test("18")); //=>false
  16. reg = /^[10-29]$/; //=>1或者0-2或者9
  17. console.log(reg.test("1"));//=>true
  18. console.log(reg.test("9"));//=>true
  19. console.log(reg.test("0"));//=>true
  20. console.log(reg.test("2"));//=>true
  21. console.log(reg.test("10"));//=>false

4. 常用的正则表达式

1. 验证是否为有效数字

  1. /*
  2. * 规则分析
  3. * 1.可能出现 + - 号,也可能不出现 [+-]?
  4. * 2.一位0-9都可以,多位首位不能是0 (\d|([1-9]\d+))
  5. * 3.小数部分可能有可能没有,一旦有后面必须有小数点+数字 (\.\d+)?
  6. */
  7. let reg = /^[+-]?(\d|([1-9]\d+))(\.\d+)?$/;
  8. // 保留两位小数
  9. let reg = /^[+-]?(\d|([1-9]\d+))(\.\d{1,2})?$/;

2. 验证密码

  1. //=>数字、字母、下划线
  2. //=>6~16位
  3. let val = userPassInp.value,
  4. reg = /^\w{6,16}$/;
  5. let flag=reg.test(val);

3. 验证真实姓名

  1. /*
  2. * 1.汉字 /^[\u4E00-\u9FA5]$/
  3. * 2.名字长度 2~10位
  4. * 3.可能有译名 ·汉字 (·[\u4E00-\u9FA5]{2,10}){0,2}
  5. */
  6. let reg = /^[\u4E00-\u9FA5]{2,10}(·[\u4E00-\u9FA5]{2,10}){0,2}$/;

4. 验证邮箱

  1. let reg = /^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/;
  2. //=> \w+((-\w+)|(\.\w+))*
  3. //1.开头是数字字母下划线(1到多位)
  4. //2.还可以是 -数字字母下划线 或者 .数字字母下划线,整体零到多次
  5. //=>邮箱的名字由“数字、字母、下划线、-、.”几部分组成,但是-/.不能连续出现也不能作为开始
  6. //=> @[A-Za-z0-9]+
  7. //1.@后面紧跟着:数字、字母 (1-多位)
  8. //=> ((\.|-)[A-Za-z0-9]+)*
  9. //1.对@后面名字的补充
  10. // 多域名 .com.cn
  11. // 企业邮箱 zxt@stone-qiaduan-office.com
  12. //=> \.[A-Za-z0-9]+
  13. //1. 这个匹配的是最后的域名(.com/.cn/.org/.edu/.net...)

5. 身份证号码

  1. /*
  2. * 1. 一共18位
  3. * 2. 最后一位可能是X
  4. *
  5. * 身份证前六位:省市县 130828
  6. * 中间八位:年月日
  7. * 最后四位:
  8. * 最后一位 => X或者数字
  9. * 倒数第二位 => 偶数 女 奇数 男
  10. * 其余的是经过算法算出来的
  11. */
  12. //let reg = /^\d{17}(\d|X)$/;
  13. //=>小括号分组的第二个作用:分组捕获,不仅可以把大正则匹配的信息捕获到,还可以单独捕获到每个小分组的内容
  14. let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/;
  15. reg.exec("130828199012040617");
  16. //=>["130828199012040617", "130828", "1990", "12", "04", "1", "7"...] 捕获结果是数组,包含每一个小分组单独获取的内容

5. 正则的捕获

1. 实现正则捕获的方法

正则RegExp.prototype上的方法

  • exec
  • test

字符串String.prototype上支持正则表达式的方法

  • replace
  • match
  • splite
    1. let str = "stone20190yangfan2020qihang2021";
    2. let reg = /^\d+$/;
    3. console.log(reg.test(str)); // => false
    4. console.log(reg.exec(str)); // => null
    实现正则的前提是:当前正则要和字符串匹配,不匹配出现的结果是null。

2. 基于exec实现正则的捕获

  • 捕获到的结果是 null 或者一个数组
    • 第一项:是本次捕获到的内容
    • 其余项:对应小分组本次单独捕获的内容
    • index:当前捕获内容在字符串中起始的位置
    • input:原始字符串
  • 每执行一次 exec,只能捕获到一个符合正则规则的,但是默认情况下,我们不管执行多少遍,获取的结果永远都是第一个匹配到的,其余的捕获不到。

    • “正则捕获的懒惰性”:默认只捕获第一个

      3. 懒惰性的原因和解决办法

  • 原因:默认情况下,lastIndex的值是不会被改变的,每一次都是从字符串的开始位置查找所以找到的永远是第一个。

  • 解决方法:我们不能手动改lastIndex,我们可以使用修饰符g,设置全局匹配修饰符g之后,lastIndex会被修改。

    1. let reg = /\d+/g;
    2. if (reg.test(str)) {
    3. //=>验证一下:只有正则和字符串匹配我们在捕获
    4. console.log(reg.lastIndex); //=>11 基于TEST匹配验证后,LASTINDEX已经被修改为第一次匹配后的结果,所以下一次捕获不再从头开始了
    5. console.log(reg.exec(str)); //=>["2020"...]
    6. }

    4. 实现一个方法execAll,执行一次可以把所有的结果都匹配到(前提是要写全局修饰符g)

    1. ~functoin() {
    2. function execAll(str = '') {
    3. // 先判读正则是否设置了修饰符g
    4. if (!this.global) return this.exec(str);
    5. // str是当前被匹配的字符串
    6. // array存放所有的捕获结果
    7. // res存放每一次捕获的结果,null或者一个数组
    8. let array = [],
    9. res = this.exec(str);
    10. while(res) {
    11. // 每一次捕获的内容都放在数组中
    12. array.push(res);
    13. // 只要捕获内容补位null,则继续捕获下去
    14. res = this.exec(str)
    15. }
    16. return array;
    17. }
    18. RegExp.prototype.execAll = execAll;
    19. }();
    20. let reg = /\d+/g;
    21. console.log(reg.execAll(str));
    22. // execAll方法可以使用字符串的match实现,execAll就是match的实现
    23. console.log(str.match(reg));
  • execAll方法可以使用字符串的match实现,execAll就是match的实现

  • 但是多次匹配的情况下,match只能把大正则匹配的内容获取到,小分组匹配的信息无法获取
    1. '-42'.match(/^([-+]?\d*).*/)
    2. (2) ["-42", "-42", index: 0, input: "-42", groups: undefined]
    3. '-42dasda'.match(/^([-+]?\d*).*/)
    4. (2) ["-42dasda", "-42", index: 0, input: "-42dasda", groups: undefined]
    5. '-42dasda'.match(/^([-+]?\d*).*/g)
    6. ["-42dasda"]

6. 正则表达式中的分组捕获和分组引用

1. 分组捕获

  1. //=>身份证号码
  2. let str = "130828199012040112";
  3. let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(?:\d|X)$/;
  4. console.log(reg.exec(str));
  5. console.log(str.match(reg));
  6. //=>["130828199012040112", "130828", "1990", "12", "04", "1", index: 0, input: "130828199012040112"]
  7. //=>第一项:大正则匹配的结果
  8. //=>其余项:每一个小分组单独匹配捕获的结果
  9. //=>如果设置了分组(改变优先级),但是捕获的时候不需要单独捕获,可以基于?:来处理
  • 第一项:大正则匹配的结果
  • 其余项:每一个小分组单独匹配捕获的结果
  • 如果设置了分组(改变优先级),但是捕获的时候不需要单独捕获,我们可以使用?:来处理,?:只分组不捕获

注意:match多次匹配的情况下,match只能把大正则匹配的内容获取到,小分组匹配的信息无法获取
解决办法:

  1. ~function() {
  2. function execAll(str = '') {
  3. if (!this.global) return this.exec(str);
  4. let arrayBig = [],
  5. arraySmall = [],
  6. res = this.exec(str);
  7. while(res) {
  8. let [big, small] = res;
  9. arrayBig.push(big);
  10. arraySmall.push(small);
  11. res = this.exec(str)
  12. }
  13. }
  14. RegExp.prototype.execAll = execAll;
  15. }();

2. 分组引用

分组引用:就是通过“\数字”让其代表和对应分组出现一模一样的内容

  1. //=>分组的第三个作用:“分组引用”
  2. let str = "book"; //=>"good"、"look"、"moon"、"foot"...
  3. let reg = /^[a-zA-Z]([a-zA-Z])\1[a-zA-Z]$/;
  4. console.log(reg.test("book")); //=>true
  5. console.log(reg.test("deep")); //=>true
  6. console.log(reg.test("some")); //=>false

7. 正则的贪婪性

1. 正则捕获的贪婪性

正则捕获的贪婪性,默认情况下,正则捕获的时候,是按照当前正则所匹配的最长结果来获取的。例如:

  1. let reg = /^\d+$/;
  2. let str = '2019@@2020';
  3. console.log(str.match(reg));// ['2019', '2020'];

2. 如何取消贪婪性

在量词元字符后面设置 ? 取消捕获的贪婪性(按照正则匹配的最短结果来匹配)

3. ? 在这正则的作用(五大作用)

  • 问号左边不是量词元字符,表示出现0到1次
  • 问号左边是量词元字符,取消正则的贪婪性
  • (?:)只匹配不捕获
  • (?=)正向预查
  • (?:)负向预查

8. 正则的其他捕获方法

1. test也能捕获(本意是匹配)

  1. let str = "{0}年{1}月{2}日";
  2. let reg = /\{(\d+)\}/g;
  3. console.log(reg.test(str)); //=>true
  4. console.log(RegExp.$1); //=>"0"
  5. console.log(reg.test(str)); //=>true
  6. console.log(RegExp.$1); //=>"1"
  7. console.log(reg.test(str)); //=>true
  8. console.log(RegExp.$1); //=>"2"
  9. console.log(reg.test(str)); //=>false
  10. console.log(RegExp.$1); //=>"2" 存储的是上次捕获的结果
  11. //=>RegExp.$1~RegExp.$9:获取当前本次正则匹配后,第一个到第九个分组的信息

2. replace 字符串中实现替换的方法(一般都是伴随正则一起使用的)

  1. let str = "stone@2019|stone@2020";
  2. //=>把"stone"替换成"前端"
  3. //1.不用正则,执行一次只能替换一个
  4. /*
  5. str = str.replace("stone","前端").replace("stone","前端");
  6. console.log(str);
  7. */
  8. //2.使用正则会简单一点
  9. str = str.replace(/stone/g,"前端");
  10. console.log(str);
  1. let str = "stone@2019|stone@2020";
  2. //=>把"stone"替换为"stone前端"
  3. //str=str.replace("stone","stone前端").replace("stone","stone前端");
  4. //"zhufengpeixunpeixun@2019|zhufeng@2020" 每一次替换都是从字符串第一个位置开始找的(类似于正则捕获的懒惰性)
  5. //=>基于正则g可以实现
  6. str = str.replace(/stone/g,"stone前端");


例子:把时间字符串进行处理

将”2020-07-01”转换成”2020年07月01日”

  • 可以这样实现

    1. let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/;
    2. let time = '2020-07-01';
    3. time = time.replace(reg, '$1年$2月$3日');
    4. console.log(time);// 2020年07月01日
  • 还可以这样处理 [str].replace([reg], [function])

    • 1、首先拿reg和字符串进行匹配捕获,能匹配几次就会把传递的函数执行几次(匹配一次执行一次)
    • 2、不仅把方法执行,而且replace还给方法传递了实参信息,和exec捕获的内容一致的信息:大正则匹配的内容,小分组匹配的信息
    • 3、执行方法中return返回的是什么,就会把大正则匹配的内容替换成什么 ``javascript let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/; let time = '2020-07-01'; time = time.replace(reg, (big, $1, $2, $3) => { // 这里的$1~$3是我们自己设置的变量 console.log(big, $1, $2, $3); //2020-07-01 2020 07 01 return${$1}年${$2}月${$3}日`; }); console.log(time); // 2020年07月01日
  1. <a name="yFlW4"></a>
  2. #### 例子:首字母大写
  3. ```javascript
  4. let str = 'good good study, day day up!';
  5. let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g;
  6. str = str.replace(reg, (...arg) => {
  7. let [content, $1] = arg;
  8. return `${$1.toUpperCase()}${content.substring(1)}`;
  9. });
  10. console.log(str); // Good Good Study, Day Day Up!

9. 正则中括号的作用

  • 一般我们写的时候都伴随着小括号进行分组,因为小括号改变处理的优先级 =>小括号
  • 小括号分组的第二个作用:分组捕获,不仅可以把大正则匹配的信息捕获到,还可以单独捕获到每个小分组的内容
  • 分组引用

结束语

如果文章中什么不对或者写的不好的地方,请大家多多指正,谢谢!码字不易,点个赞加个关注吧!
靓仔.gif

题外话

笔者在「深圳虾皮」,一家口碑还不错的东南亚电商公司,2021大量招人,机会多多!快来加入我们吧!

现在有想法,还是以后有想法的同学,都可以加我微信[stone—-999]!内推你加入我们的大家庭!