一、正则介绍

正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑

正则的引擎大致可分为两类:DFA和NFA
  1. DFA (Deterministic finite automaton) 确定型有穷自动机
  2. NFA (Non-deterministic finite automaton)非确定型有穷自动机,大部分都是NFA。

例子: 比如有字符串this is muchen’s blog,正则表达式为 /mu(shen|chen|chem)/;
NFA先在字符串中查找 m 然后匹配其后是否为 u ,如果是 u 则继续,查找其后是否为 s 如果不是则匹配其后是否为 c (此时淘汰shen选择支)。然后继续看其后是否依次为 h,e,接着测试是否为 n ,是 n 则匹配成功,不是则测试是否为 m 。NFA工作方式是以正则表达式为标准,反复测试字符串,这样同样一个字符串有可能被反复测试了很多次!
DFA会从 this 中 t 开始依次查找 m,定位到 m ,已知其后为u,则查看表达式是否有 u ,此处正好有u 。然后字符串u 后为c ,DFA依次测试表达式,此时 shen 不符合要求淘汰。chen 和 chem 符合要求,然后DFA依次检查字符串,检测到che 中的 n 时只有chen 分支符合,则匹配成功!
两种引擎的工作方式完全不同,一个(NFA)以表达式为主导,一个(DFA)以文本为主导;

二、正则基础

语法:

  1. /正则表达式主体/修饰符(可选)

修饰符:

修饰符 说明
i 执行对大小写不敏感的匹配
g 执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)
m 执行多行匹配
n Unicode模式
y 全局匹配(区别于g)

元字符(拥有特殊含义的字符)

元字符 描述
· 查找单个字符,除了换行符和行结束符
\w 查找单词字符
\W 查找非单词字符
\d 查找数字(同 /[0-9]/)
\D 查找非数字字符(同 /[^0-9]/)
\s 查找空白字符
\S 查找非空白字符
\b 匹配单词边界
\B 匹配非单词边界
\0 查找NULL字符
\n 查找换行符
\f 查找换页符
\r 查找回车符
\t 查找制表符
\v 查找垂直制表符
\xdd 查找以十六进制dd规定的字符
\uxxxx 查找以十六进制数xxxx规定的Unicode字符

量词(用于表示重复次数的含义)

量词 描述
n+ 匹配任何包含至少一个n的字符串
n* 匹配任何包含零个或多个n的字符串
n? 匹配任何包含零个或一个n的字符串
n{X} 匹配包含X个n的序列的字符串
n{X,} X是一个正整数,前面的模式n连续出现至少X次时匹配
n{X,Y} X和Y为正整数,模式n连续出现至少X次,至多出现Y次时匹配
n$ 匹配任何结尾为n的字符串
^n 匹配任何开头为n的字符串

括号修饰符(用于查找某个范围内的字符)

表达式 描述
[abc] 查找方括号中的所有字符
[^abc] 查找任何不在方括号之间的字符
[0-9] 查找任何从0到9的数字
[a-z] 查找任何从小写a到小写z之间的字符
[A-z] 查找任何从大写A到小写z之间的字符
[red | blue | green] 查找任何指定的选项(中括号内,竖线表示普通的字符)
a(bc) 匹配abc字符
a(b | c) 匹配ab或ac字符串
(\n) 获取第n个捕获组里面的内容
  1. //正则表达式中的小括号"()",是代表分组的意思。 如果再其后面出现\1则是代表与第一个小括号中要匹配的内容相同
  2. let dateList = `
  3. 2017-10-10
  4. 2017-11-2017
  5. 2017-12-12
  6. `
  7. dateList.match(/^(\d{4})-(\d{2})-(\1)/gm)
  8. >>> ["2017-11-2017"]
  9. dateList.match(/^(\d{4})-(\d{2})-(\2)/gm)
  10. >>> ["2017-10-10", "2017-12-12"]
  11. /^(\d)\1{2}([a-z])\2{2}/g.test('111aaab')
  12. >>> true

捕获组(用变量来调用匹配到的值)

捕获组 描述
(\d+) 括号中的被称之为捕获组
(?\d+) 命名name捕获组(新方法)

非捕获组(规则会被命中,但是在结果中不会包含它)

非捕获组 描述
?=n 匹配任何其后紧接指定字符串n的字符串 (正向前瞻)
?!n 匹配任何其后没有紧接指定字符串n的字符串 (负向前瞻)
?<=n 匹配任何其前紧接指定字符串n的字符串 (正向后瞻 - 部分浏览器支持度不好)
?<!n 匹配任何其前没有紧接指定字符串n的字符串 (负向后瞻 - 部分浏览器支持度不好)
  • 前瞻是非捕获性的:其特征是无法引用。
  • 前瞻不消耗字符:前瞻只匹配满足前瞻表达式的字符,而不匹配其本身。
    1. let str = "my name is <muchen>, i like <guitar> and <music>!";
    2. str.match(/<.+?>/g)
    3. >>> ["<muchen>", "<guitar>", "<music>"]
    4. str.match(/(?<=<).+?(?=>)/g)
    5. >>> ["muchen", "guitar", "music"]
    ReExp对象方法
方法 描述
compile 编译正则表达式(用于在脚本执行过程中编译正则表达式,也可用于改变和重新编译正则表达式)
exec 检索字符串中指定的值,并确定其位置
test 检索字符串中指定的值,返回true或false
toString 返回正则表达式的字符串值
  1. let str = 'man an woman';
  2. let reg = /man/g;
  3. console.log(str.replace(reg, "person"))
  4. >>> person an woperson
  5. let reg = /man/g;
  6. let reg1 = /(wo)?man/g;
  7. reg.compile(reg1);
  8. console.log(str.replace(reg, "person"))
  9. >>> person an person
  1. /[chen]/.exec('my name is muchen')
  2. >>> ["n", index: 3, input: "my name is muchen", groups: undefined]
  3. // groups用来存储命名捕获组的信息
  4. let _reg = /(?<first>mu)(?<second>chen)/
  5. _reg.exec('my name is muchen')
  6. >>> (3) ["muchen", "mu", "chen", index: 11, input: "my name is muchen", groups: {…}]
  7. >>> 0: "muchen"
  8. >>> 1: "mu"
  9. >>> 2: "chen"
  10. >>> groups: {first: "mu", second: "chen"}
  11. >>> index: 11
  12. >>> input: "my name is muchen"
  13. >>> length: 3
  14. >>> __proto__: Array(0)
  15. _reg.exec('my name is muchen').groups
  16. >>> {first: "mu", second: "chen"}
  17. /my/ig.exec('My name is muchen my')
  18. >>> ["My", index: 0, input: "My name is muchen my", groups: undefined]
  1. /[chen]/.test('my name is muchen')
  2. >>> true
  3. var reg = /(\d{4})-(\d{2})-(\d{2})/;
  4. var dateStr = '2021-03-11';
  5. reg.test(dateStr);
  6. >>> true // 执行后下面的代码才会有效
  7. RegExp.$1 >>> "2021"
  8. RegExp.$2 >>> "03"
  9. RegExp.$3 >>> "11"
  10. // 使用例子
  11. let format = function(format) {
  12. // eg:format="yyyy-MM-dd hh:mm:ss";
  13. var o = {
  14. "M+" :this.getMonth() + 1, // month
  15. "d+" :this.getDate(), // day
  16. "h+" :this.getHours(), // hour
  17. "m+" :this.getMinutes(), // minute
  18. "s+" :this.getSeconds(), // second
  19. "q+" :Math.floor((this.getMonth() + 3) / 3), // quarter (季度)
  20. "S" :this.getMilliseconds()
  21. }
  22. if (/(y+)/.test(format)) {
  23. // RegExp.$1 : 取正则表达式中第一个分组匹配到的内容
  24. format = format.replace(RegExp.$1, (this.getFullYear() + "") .substr(4 - RegExp.$1.length));
  25. }
  26. for ( var k in o) {
  27. if (new RegExp("(" + k + ")").test(format)) {
  28. format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
  29. }
  30. }
  31. return format;
  32. }
  1. /muchen/g.toString()
  2. >>> "/muchen/g"

String对象的方

方法 描述
search 检索与正则表达式相匹配的值
match 找到一个或多个正则表达式的匹配
replace 替换与正则表达式匹配的字符
split 把字符串分割为字符串数组
  1. // search 返回与正则表达式查找内容匹配的第一个子字符串的位置, 没有匹配返回-1
  2. 'my name is muchen'.search(/muchen/)
  3. >>> 11
  1. // match 在字符串内检索指定的值,或找到一个或多个正则表达式的匹配
  2. 'my name is muchen my'.match(/my/)
  3. >>> ["my", index: 0, input: "my name is muchen my", groups: undefined]
  4. 'My name is muchen my'.match(/my/ig)
  5. >>> ["My", "my"]
  6. // groups用来存储命名捕获组的信息
  7. "2012-03-11".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/).groups
  8. >>> {year: "2012", month: "03", day: "11"}
  9. // 通过RegExp.$1获取值
  10. 'My name is muchen my'.match(/(m\w{1})\s(is)/)
  11. >>> RegExp.$1 ---> "me"
  12. >>> RegExp.$2 ---> "is"
  1. // replace 用指定的字符串替换字符串中与正则表达式匹配的子字符串
  2. 'my name is muchen'.replace(/muchen/,"taoYeah")
  3. >>> "my name is taoYeah"
  4. "hi muchen, my name is muchen too".replace(/muchen/, 'taoyeah')
  5. >>> "hi taoyeah, my name is muchen too"
  6. "hi muchen, my name is muchen too".replace(/muchen/g, 'taoyeah')
  7. >>> "hi taoyeah, my name is taoyeah too"
  8. '2021/03/11'.replace(/(\d{4})\/(\d{2})\/(\d{2})/, '$2-$3-$1')
  9. >>> "03-11-2021"
  10. '03-11-2021'.replace(/(?<month>\d{2})-(?<day>\d{2})-(?<year>\d{4})/, "$<year>-$<month>-$<day>")
  11. >>> "2021-03-11"
  12. // replace 的第二个参数是函数时:
  13. // 1、正则没有分组的时候,传进去的第一个实参是正则捕获到的内容,第二个参数是捕获到的内容在原字符串中的索引位置,第三个参数是原字符串(输入字符串)
  14. 'my name is muchen'.replace(/m(y|u)/g, function(...args) {
  15. console.log(args)
  16. return args[0].toUpperCase();
  17. })
  18. >>> ["my", "y", 0, "my name is muchen"]
  19. >>> ["mu", "u", 11, "my name is muchen"]
  20. >>> "MY name is MUchen"
  21. // 2、当正则有分组的时候,第一个参数是总正则查找到的内容,后面依次是各个子正则查找到的内容
  22. '12ab34cd567efg8h'.replace(/(\d)(\d)/g,function(...args){
  23. console.log(args)
  24. return Number(args[1])+Number(args[2]);
  25. });
  26. >>> ["12", "1", "2", 0, "12ab34cd567efg8h"]
  27. >>> ["34", "3", "4", 4, "12ab34cd567efg8h"]
  28. >>> ["56", "5", "6", 8, "12ab34cd567efg8h"]
  29. >>> "3ab7cd117efg8h"
  1. 'my name is muchen'.split(' ')
  2. >>> ["my", "name", "is", "muchen"]
  3. 'my name is muchen'.split(' ', 2)
  4. >>> ["my", "name"]

exec和match的区别

  1. exec是正则表达式的方法,不是字符串的方法,它的参数是字符串;
  2. match是字符串执行匹配正则表达式规则的方法,他的参数是正则表达;
  3. 如果定义正则表达对象为全局匹配,match执行了全局匹配查询,而exec只会找到一个匹配的即返回;若不是全局匹配,则两者一样;

    1. let reg = /ab/g;
    2. let str = "1abc2,3abc4";
    3. console.log(reg.exec(str));
    4. console.log(str.match(reg));
    5. >>> ["ab", index: 1, input: "1abc2,3abc4", groups: undefined]
    6. >>> (2) ["ab", "ab"]
    1. let reg = /ab/;
    2. let str = "1abc2,3abc4";
    3. console.log(reg.exec(str));
    4. console.log(str.match(reg));
    5. >>> ["ab", index: 1, input: "1abc2,3abc4", groups: undefined]
    6. >>> ["ab", index: 1, input: "1abc2,3abc4", groups: undefined]
  4. 当正则表达式有子表达式时,并且定义为全局匹配,exec和match执行的结果不一样,此时match将忽略子表达式,只查找全匹配正则表达式并返回所有内容;非全局匹配,exec和match执行的结果是一样;

    1. let reg = /a(b)/g;
    2. let str = "1abc2,3abc4";
    3. console.log(reg.exec(str));
    4. console.log(str.match(reg));
    5. >>> ["ab", "b", index: 1, input: "1abc2,3abc4", groups: undefined]
    6. >>> ["ab", "ab"]
    1. let reg = /a(b)/;
    2. let str = "1abc2,3abc4";
    3. console.log(reg.exec(str));
    4. console.log(str.match(reg));
    5. >>> ["ab", "b", index: 1, input: "1abc2,3abc4", groups: undefined]
    6. >>> ["ab", "b", index: 1, input: "1abc2,3abc4", groups: undefined]

    贪婪模式与非贪婪模式

  • 贪婪模式——在匹配成功的前提下,尽可能多的去匹配 (默认模式);
  • 非贪婪模式——在匹配成功的前提下,尽可能少的去匹配; ``` let str = “my name is , i like and !”; // <—贪婪模式—> str.match(/<.+>/g)

    [“, i like and “] // ————————————————————————————- // 先根据正则的第一个字符 < 进行寻找匹配,后面的 .可以匹配除换行符外的 // 全部字符,于是它就一直匹配到了最后,直到文本结束

  1. <………………………………. “my name is , i like and !” // .号匹配完毕后,开始匹配后面的 >,于是正则/<.+>/开始往回匹配感叹号 >, // 一直匹配到 ! 后面的 >,发现符合规则了,于是匹配出来的就是这一串字符串 // [, i like and ]
  2. <……………………………..> “my name is , i like and !” // ========================================================= // <—非贪婪模式—> str.match(/<.+?>/g)

    [““, ““, ““] // ————————————————————————————- // 与贪婪模式一样先匹配 <,然后进行.的匹配,但是与贪婪模式不同的是,它会 // 以最小的.的重复数进行匹配。每匹配一次.,就会往后匹配一次 > ;

  3. <……> “my name is , i like and !”

// 因为g是全局匹配,所以又会从正则头开始匹配第一个 <,到了 后, // 匹配成功第一个 < 和六个.,以及后面的 >,于是也被匹配上了,然 // 后重复查找,结果为:[““, ““, ““] // ========================================================= str.match(/(?<=<).+?(?=>)/g) // 使用正向后瞻和正向前瞻拿到指定的内容

[“muchen”, “guitar”, “music”] ``` 新增正则修饰符 u、y

  • ES6 对正则表达式添加了 u 修饰符,含义为 “Unicode模式”,用来正确处理大于 \uFFFF 的Unicode字符。也就是说,会正确处理四个字符的 UTF-16 编码。ES6 新增了使用大括号表示 Unicode 字符,这种表示法在正则表达式中必须加上u修饰符,才能识别当中的大括号,否则会被解读为量词。

    1. /\u{61}/.test('a') // false --> 61个u
    2. /\u{61}/u.test('a') // true --> 字母a
    3. /𠮷{2}/.test('𠮷𠮷') // false -->不能识别‘’
    4. /𠮷{2}/u.test('𠮷𠮷') // true
  • y修饰符和g修饰符是类似的,都是全局匹配,但y修饰符有一定的匹配要求g修饰符只要剩余的字符中存在匹配即可y修饰符必须从剩余字符的第一个位置开始匹配,否则退出匹配。

    1. let str = "aaa_aa_aaaa"
    2. let reg_g = /a+/g
    3. let reg_y = /a+/y
    4. reg_g.exec(str) // aaa
    5. reg_y.exec(str) // aaa
    6. reg_g.exec(str) // aa
    7. reg_y.exec(str) // null

    第一次 regy.exec 的时候,匹配的是“aaa_aa_aaaa”字符,从第一个位置开始匹配并且匹配成功:aaa;
    第二次 reg_y.exec 的时候,匹配的是“_aa_aaaa”字符,从第一个位置 “
    ” 开始匹配,但匹配不成功,因此退出匹配;
    通过自定义匹配位置lastIndex,进行开始匹配位置的修改:

    1. let str = "aaa_aa_aaaa";
    2. let reg_g = /a+/g;
    3. reg_g.exec(str) ---> ["aaa", index: 0, input: "aaa_aa_aaaa", groups: undefined]
    4. reg_g.lastIndex ---> 3
    5. reg_g.exec(str) ---> ["aa", index: 4, input: "aaa_aa_aaaa", groups: undefined]
    6. reg_g.lastIndex ---> 6
    7. reg_g.exec(str) ---> ["aaaa", index: 7, input: "aaa_aa_aaaa", groups: undefined]
    8. reg_g.lastIndex ---> 11
    9. // --------------------------------------------------------------------------
    10. reg_g.lastIndex = 2
    11. reg_g.exec(str)
    12. >>> ["a", index: 2, input: "aaa_aa_aaaa", groups: undefined]
    13. reg_g.lastIndex ---> 3
    1. let str = "aaa_aa_aaaa"
    2. let reg_y = /a+/y
    3. reg_y.exec(str) ---> ["aaa", index: 0, input: "aaa_aa_aaaa", groups: undefined]
    4. reg_y.exec(str) ---> null
    5. reg_y.lastIndex ---> 0
    6. // --------------------------------------------------------------------------
    7. reg_y.exec(str) ---> ["aaa", index: 0, input: "aaa_aa_aaaa", groups: undefined]
    8. reg_y.lastIndex = 4
    9. reg_y.exec(str)
    10. >>> ["aa", index: 4, input: "aaa_aa_aaaa", groups: undefined]
    11. reg_y.lastIndex = 7
    12. reg_y.exec(str)
    13. >>> ["aaaa", index: 7, input: "aaa_aa_aaaa", groups: undefined]