循序渐进,通过40个正则我终于入门了正则表达式

简介: 最近在阅读axios中的工具函数的源码,说实话学到了很多知识,只要看不懂的我就不断的查阅资料,进行自我校验、自我巩固和自我讲解,总之就是不断的将自己不懂的基础知识挖深学习。因为之前查看源码的一段时间,保证的只是能看懂源码的逻辑就算是不错了,学习的深度还不够,没有真正的抓住源码的思想精髓。

image.png


大家好,我是 那个曾经的少年回来了。10年前我也曾经年轻过,如今已步入被淘汰的年龄,但现在幡然醒悟,所以活在当下,每天努力一点点,来看看2024年的时候自己会是什么样子吧,2024年的前端又会是什么样子,而2024年的中国乃至全球又会变成什么样子,如果你也有想法,那还不赶紧行动起来。期待是美好的,但是更重要的是要为美好而为之奋斗付诸于行动。


前言


最近在阅读axios中的工具函数的源码,说实话学到了很多知识,只要看不懂的我就不断的查阅资料,进行自我校验、自我巩固和自我讲解,总之就是不断的将自己不懂的基础知识挖深学习。因为之前查看源码的一段时间,保证的只是能看懂源码的逻辑就算是不错了,学习的深度还不够,没有真正的抓住源码的思想精髓。


阅读源码一部分是可以看到作者代码功底的深厚程度,看清作者如何通过一些基础融会贯通、想出一些奇思妙想,认清自己当前跟作者的差距,不断的夯实和优化自己,学习作者的思维习惯,不断的模仿优秀的开源大师,等到来年定能在一个小角落开花结果,所以稍安勿躁 切莫着急。


axios工具函数中,有两个方法使用了正则表达式来处理,痛定思痛。我下定决心想将这正则表达式蹂躏于脚下,让我从此不再惧怕看到正则,以前都是只会往上搜索然后直接使用,想看别人写的正则,真的门都没有。


但是经过这几天的摸索和学习我才发现,原来看上去那么高大上的正则也不过如此,掌握了一些规则和语法,便可以轻松的搞明白别人的正则想法。所以如果你也曾被正则困扰过,那么来看看我的学习思路,或许能给你带来一些自信,让你能有勇气去重新战胜它。 好了废话就不多说了,开始吧。


axios工具函数中有一个移除字符串首尾空格的方法,先判断是否包含trim方法,如果有直接调用trim方法进行移除,这是JavaScript自带的方法。 当然有些环境是不存trim方法的(比如IE8及以下版本是不支持trim方法的),就需要通过后面的正则进行去除首尾的空格以及特殊字符。


//去除空格的方法涉及正则
const trim = (str) => str.trim ?
  str.trim() : str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');


字符串方法 replace的第一个参数 /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g便是正则表达式。


// 将字符串转换为CameCase 驼峰式命名
const toCamelCase = str => {
  return str.toLowerCase().replace(/[_-\s]([a-z\d])(\w*)/g,
    function replacer(m, p1, p2) {
      return p1.toUpperCase() + p2;
    }
  );
};


同样的还有一个方法 将字符串转换为CameCase 驼峰式命名中也使用了正则/[_-\s]([a-z\d])(\w*)/g


其实现在来看这两个正则表达式,如果是像我一样之前没怎么接触过的话,肯定是一头雾水,什么玩意哟。那接下来我就通过我自己学习的方式,来慢慢的揭开正则表达式的神秘面纱。循序渐进,将所有的正则基础都做一个了解。


  • ^ 相当于开头
  • \s匹配空白字符(空格、制表符、其他空白)\xA0 代表空格的意思。 在 IE 早期版本中,异步接口可能会拿到 \uFEFF 开头的字符串,如果不 trim 掉,会导致 JSON.parse 失败。


1、从最简单的正则开始


1.1、匹配出单个字符


匹配字母a是否存在于字符串


let reg = /a/
reg.test('apple')   // true
reg.test('pear')  // true
reg.test('bed')   // false


1.2、匹配出多个字符(或者叫字符串)


匹配字符串 world是否存在于字符串


let reg = /world/
reg.test('hello world')   // true
reg.test('good morning')  // false


1.3、匹配某个字符串出现的次数


匹配 hello 字符串在 大字符串中出现的次数


  • i 忽略字符串大小写
  • g 全局匹配


let reg = /hello/ig
let str = 'hello world。 Hello, in a new world'
str.match(reg)   //(2) ['hello', 'Hello']


1.4 替换字符串


let reg = /hello/ig
let str = 'hello world。 Hello, in a new world'
str.replace(reg, "aehyok")   // 替换完输出 'aehyok world。 aehyok, in a new world'


  • i 忽略大小写替换字符串,所以会替换掉原来的 helloHello
  • g 全局范围内查找


2、匹配特殊字符


let reg = /\*/
reg.test('apple*')   // true
reg.test('pear*')  // true
reg.test('bed')   // false


你可以去尝试一下,如果不加这个斜杠就会报错的


image.png


这里面的*就算是一个特殊字符,就是这个字符其实在正则表达式中存在着特殊的语义。目前在正则中有特殊意义的字符有如下几个:( [ { \ ^ $ | ) ] } ? * + . 。 另外这里还有一类特殊字符:


特殊字符 正则表达式
空白符 \s
换行符 \n
回车符 \r
制表符 \t
垂直制表符 \v
换页符 \f


空白符包括: 空格、水平制表符、垂直制表符、换行符、回车符。 \s(小s) 包括 [\r\n\t\f\v ])。\S(大S) 是[^\r\n\t\f\v],非空白符。


使用的时候如下所示,只要有空格就可以匹配到,或者可以测试其他字符


let reg = /\n/
reg.test('apple\n*')   // false
reg.test('apple*')   // true


3、两种使用模式


字面量模式和字符串模式两种正则表达式的模式,最终达到的效果是一样的


3.1、字面量模式


判断是否以[aehyok]开头


// 字面量模式
let reg = /^\[aehyok\]/
reg.test('[aehyok]123')  // true
reg.test('[aeh1k]123')  // false


3.2、字符串模式


判断是否以[aehyok]开头


let reg = new RegExp('^\\[aehyok\\]')
reg.test('[aehyok]123')  // true
reg.test('[aeh1k]123')  // false


  • ^表示以什么开头的,专门用来匹配开头位置的,在匹配位置说明中,我也会进行实例讲解


4、字符匹配的次数


4.1、({m,n})连续出现最少m次,最多n次


let reg = /abc{2,4}d/g
reg.test("abcgg")   // false 只匹配一个c
reg.test("abccgg")   // true  能匹配两个c
reg.test("abccccgg")  //true  能匹配四个c
reg.test("abcccccgg")  ///false 超过四个c 没办法了


4.2、(?)匹配前面的子表达式出现0次或者1次,或者另外一种类型({0,1})


let reg = /abcde?f/
let reg = /abcde{0,1}f/
reg.test('abcdef') // true
reg.test('abcdf') // true
reg.test('abcdeef') // false


第一个出现了1次 第二个出现了0次 第三个出现了2次,这个就出问题了,为false


4.3、(+)匹配前面的子表达式1次或多次,或者另外一种类型({1,})


let reg = /[1-9][0-9]+/
let reg = /[1-9][0-9]{1,}/
reg.test("2")  // false
reg.test("2a")  // false
reg.test("21")  // true
reg.test("212")  // true


匹配后面的正则表达式[0-9] 一次或者多次,对于整个正则表达式来说,也可以说成是子表达式


4.4、(*)匹配前面的子表达式0次或多次,或者另外一种类型({0,})


let reg = /[1-9]*/
let reg = /[1-9]{0,}/
reg.test("")  // true
reg.test("1")  // true
reg.test("12")  // true


4.5、变种({m,m})只出现过m次


m为数字


  • 四位数字


let reg = /^[0-9]{4}$/
reg.test("1234")  // true
reg.test("12s4")  // false
reg.test("12345") // false


5、匹配位置说明


5.1、^ 开头位置的说明


// 以1234开头的字符串
let reg = /^1234/
reg.test('12345') // true
reg.test('123s5') // false
reg.test('123')  // false


5.2、$ 结尾位置的说明


// 以1234结尾的字符串
let reg = /1234$/
reg.test('12345') // false
reg.test('111234') // true


5.3、(\b) 查看字符串中是否有单词开始abc


let reg = /\babc/
reg.test('hello abcdef')    // true
reg.test('hello world zabcd') // false
reg.test('hello world zdefabc') 


5.4、 (\b) 查看字符串中是否有单词以abc结尾的


let reg = /abc\b/
reg.test('hello abcdef')    // false
reg.test('hello world zabcd') // false
reg.test('hello world zdefabc')   // true


5.5、匹配单词中间部分


  • (\B) 刚好是 \b 的反面


既不能匹配开头部分,也不能匹配结尾部分


let reg = /\Babc/
reg.test('hello abcdef')     // false
reg.test('hello world zabcd')  // true
reg.test('hello world zdefabc')   // false


5.5、 (g和m)全局模式和多行模式


  • 普通模式


let reg = /abc/
let str = "abcd abcdd   \nabcd \nabcde" 
str.match(reg)   //['abc', index: 0, input: 'abcd abcdd   \nabcd \nabcde', groups: undefined]


这样只会匹配出第一个


  • (g)全局模式


let reg = /abc/g
let str = "abcd abcdd   \nabcd \nabcde aabc" 
str.match(reg)   // ['abc', 'abc', 'abc', 'abc', 'abc']


全局模式下,会匹配到五个。不加全局的话,匹配到就结束匹配了,而加上全局模式,会将所有能匹配到的都匹配出来


  • (m)多行模式


查看以abc开头,这里是一个字符串,并且我在中间加了两个\n 换行符。


let reg = /^abc/g   //^表示位置,开始的位置
let str = "abcd abcdd   \nabcd \nabcde aabc" 
str.match(reg)   // ['abc']


这样只会匹配到一个,只是一个字符串,并且以abc开头,没啥问题


现在我们再在上面的小例子中添加上m多行模式的匹配会是什么结果呢?


let reg = /^abc/gm   //^表示位置,开始的位置
let str = "abcd abcdd   \nabcd \nabcde aabc" 
str.match(reg)   //  ['abc', 'abc', 'abc']


我加入\n 换行符,相当于一个多行的文本,这样去执行正则的时候相当于每一行都会去单独执行正则进行匹配。 这里相当于分成了三行进行匹配


  • 组合模式


还是上面同一个字符串,现在假如我们想匹配出四个abc,四个以abc开头的单词,不是三个 也不是五个


let reg = /\babc/gm   //^表示位置,开始的位置
let str = "abcd abcdd   \nabcd \nabcde aabc" 
str.match(reg)   //  ['abc', 'abc', 'abc', 'abc']


\b单词的边界控制


m 开启多行模式匹配


5.6、(i)忽略大小写


let reg = /aBc/g
let str = "aBc  aaBc aabc"
str.match(reg) // (2) ['aBc', 'aBc']


没有加i标识,也就是没有开启忽略大小写的时候


下面是加i标识的匹配,通过结果可以看出匹配到三个


let reg = /aBc/gi
let str = "aBc  aaBc aabc"
str.match(reg) // (3) ['aBc', 'aBc', 'abc']


6、多个字符匹配


6.1、(\d) 表示一位数字


匹配到一位数字, 等同于[0-9]


let reg = /\d/
let reg = /[0-9]/
reg.test('1s')  // true
reg.test('ss')  // false


[] 表示匹配的字符在 [],并且只能出现一次,所以 [0-9] 表示 从0到9出现任何一个数字即可匹配。


6.2、(\D)表示除数字以外的任意字符


匹配除数字意外的任意字符, 等同于[^0-9]


let reg = /\D/
let reg = /[^0-9]/
reg.test('1234')  // false
reg.test('ss')  // true


6.3、(.)除了换行符以外的任何字符


let reg = /./g
reg.test("\n")  // false 只有换行符肯定为false
reg.test("1")   // true 能匹配到不是换行符的便为true


6.4、(\w)匹配字母加数字加下划线_


let reg = /[0-9a-zA-z_]{4}/
reg.test("a1b_")  // true
reg.test("a1=+")  // false


简化模式如下


let reg = /\w{4}/
reg.test("a1b_")  // true
reg.test("a1=+")  // false


6.5、(\W)非单词字符下划线


匹配出四个字符


let reg = /\W{4}/
reg.test("=-+`")  // true
reg.test("====")  // true
reg.test("____")  // false


6.6、(\s)匹配空白符和(\S)匹配非空白符


空白符包括: 空格、水平制表符、垂直制表符、换行符、回车符。 \s(小s) 包括 [\r\n\t\f\v ])。\S(大S) 是[^\r\n\t\f\v],非空白符。


特殊字符 正则表达式
空白符 \s
换行符 \n
回车符 \r
制表符 \t
垂直制表符 \v
换页符 \f


let reg = /\s/


7、贪婪匹配和懒惰匹配


在正则表达式中,表示字符串重复个数的元字符 ?,+,*,{}  默认都会选择贪婪模式,会进行最大程度的匹配。 上面其实专门说过,这里再简单总结一下这几个 元字符


  • ? 匹配前面的子表达式出现0次或者1次,或者另外一种类型({0,1})


  • + 匹配前面的子表达式1次或多次,或者另外一种类型({1,})


  • * 匹配前面的子表达式0次或多次,或者另外一种类型({0,})


  • {m,n} 连续出现最少m次,最多n次


如果想切换到懒惰模式,就只需要在该元字符后面加多一个 ?, 就代表切换到懒惰模式。 所以,贪婪模式就是尽可能多的匹配字符,而懒惰模式或者叫非贪婪模式,就是尽可能少的匹配字符。


我们来看一个简单的小例子, 从字符串 abczsssssabbbz 中匹配出axxz也就是 az 之间至少有一个字符


let reg = /a.+z/
let str = 'abczsssssabbbz'
str.match(reg)   // ['abczsssssabbbz', index: 0, input: 'abczsssssabbbz', groups: undefined]


通过结果可以发现,会匹配出整个字符串,这样可能不是我们想要的结果,因为通过最开头的几个字符可以看出已经可以匹配了abcz,但这就是贪婪匹配匹配出尽可能多的字符,那我们想匹配出 abcz,应该怎么处理呢?


let reg = /a.+?z/
let str = 'abczsssssabbbz'
str.match(reg)   // ['abcz', index: 0, input: 'abczsssssabbbz', groups: undefined]


通过在量词符号 + 后添加一个 ? 就可以表示为懒惰匹配了。意思就代表尽可能少的匹配,那么在匹配 abczsssssabbbz 最开头的几个字符就满足要求了,就不会继续往后匹配了。


8、高阶用法


8.1、()子表达式


通过() 元字符所包含的正则表达式被分为一组,每个分组就是一个子表达式了。如果要发挥子表达式强大的作用,一般会结合回溯引用才会发挥其作用。


let reg = /(ab){1,3}(cd)/g
let str = 'abcd ababcdef abababcd ababababcd acd aacd bbcd'
str.match(reg)   // ['abcd', 'ababcd', 'abababcd', 'abababcd']


上面的正则 (ab){1,3}(cd) 便是(ab) 作为第一个 分组 或者 子表达式 出现至少1次,最多3次,然后后面还有一个分组 (cd) 后没有次数,那么匹配一次就可以了。所以最终的结果就是匹配成功四组,第四组可能很多人奇怪,其实是正常的,因为第四组字符串 ababababcd ab重复四次,从这个字符串中可以找到匹配了三次 ab然后再加上 cd,也就是从这个字符串第三个位置开始匹配就能匹配成功了。


那么现在假如我们只想匹配前三个怎么办呢,其实很简单


let reg = /\b(ab){1,3}(cd)/g
let str = 'abcd ababcdef abababcd ababababcd acd aacd bbcd'
str.match(reg)   // ['abcd', 'ababcd', 'abababcd']


我只是在正则最前面加上了 \b,相信看过前面的可能都还记得 \b 就是匹配单词开头部分的,只想匹配单词非开头或者非结尾,也就是单词中间部分的话可以使用 \B,如果忘记了,可以回看番看一下。


8.2、回溯引用


回溯引用指的是模式的后面部分引用前面已经匹配到的子字符串。这里可以把它当做变量,类似的用法就像是 \1\2 以此类推。\0代表整个正则表达式。


let reg = /\b(\w+)\s\1/g
let str = 'Hello the new new world , I I am is a big big old'
str.match(reg)  // (3) ['new new', 'I I', 'big big']


  • \b 所在位置可以匹配单词开头
  • () 代表一个子表达式,其中是 \w+ 匹配字母加数字加下划线_, + 表示匹配一次或多次
  • \s 匹配空白字符(空格、制表符、其他空白)
  • \1 引用前面表达式 \b(\w+)\s 已经匹配的字符串,再次匹配  也就是匹配两次


9、总结


正则表达式在日常的开发中还是经常使用到的,但是大部分的时间都是在网上查找现成的来使用,有时候想稍微调整一下都非常困难,因为根本不了解正则表达式的一些基础知识。


通过最近的学习我发现入门正则表达式还是非常简单的,就是把一些基础的正则写出来,然后根据字符串去校验一下,再通过一些实际的例子去巩固自己的正则基础。当然目前我对正则一些高阶的用法理解的还不够透彻,还需要一段时间的消化和吸收。


不过现在说真的看别人写过的正则最起码能看懂,而且也能简单的进行修改,还是非常有成就感的。


所以文章开头第一个正则去除字符串前后的空格还是非常好理解的。而两个方法都使用了替换函数 String.prototype.replace()


str.replace(regexp|substr, newSubStr|function)


先举个最简单的例子


let reg = /o/
let str = 'hello world'
let newStr = str.replace(reg, 'zz')
console.log(newStr) // hellzz world


这里匹配替换掉了第一个ozz,如果想将 world中的 o也进行替换稍做调整即可


let reg = /o/g
let str = 'hello world'
let newStr = str.replace(reg, 'zz')
console.log(newStr) // hellzz wzzrld


上面会匹配替换第一个即停止替换了,而加上 g 进行全局匹配替换


再来说一下 replace 方法第二个参数为函数的情况,匹配成功后,函数的返回值就会作为替换字符串。


let reg = /o/g
let str = 'hello world'
let newStr = str.replace(reg,     
    function replacer(m, p1) {
      console.log(m, p1);  // o 4   // o 7
      return 'zzz';
    })
console.log(newStr) // hellzz wzzrld



image.png


const toCamelCase = str => {
  return str.toLowerCase().replace(/[_-\s]([a-z\d])(\w*)/g,
    function replacer(m, p1, p2) {
      console.log(m, p1, p2)
      return p1.toUpperCase() + p2;
    }
  );
};
const str = toCamelCase("_HelloWorld")
console.log(str)    //Helloworld


这里其实默认只匹配一个单词的,然后将首字母大写,剩余的首字母后全部小写


  • 那么这里的 p1 相当于第一个字表达式([a-z\d]) 匹配的
  • p2 就当于第二个子表达式 (\w*) 中匹配的


P1匹配出首字母以后,通过 p1.toUpperCase() 将首字母大写,然后 P2 匹配出剩余字母保持全小写的状态,我这里故意用了两个单词,但是对于程序来说,它只会作为一个单词来考虑。


这里除了 String.prototype.replace() 替换函数外,还有三个,有兴趣的可以去mdn详细学习


  • String.prototype.match() : 匹配字符串,返回匹配的数组或null
  • String.prototype.test() : 监测字符串的正则匹配,返回匹配的数组或null
  • String.prototype.search() : 查找指定字符串,返回第一个匹配的起始位置


我的个人博客:vue.tuokecat.com/blog

我的个人github:github.com/aehyok

我的前端项目:pnpm + monorepo + qiankun + vue3 + vite3 + 工具库、组件库 + 工程化 + 自动化

不断完善中,整体框架都有了

在线预览:vue.tuokecat.com

github源码:github.com/aehyok/vue-…

目录
相关文章
|
7月前
|
机器学习/深度学习 前端开发 Windows
【夯实技术基本功】「底层技术原理体系」全方位带你认识和透彻领悟正则表达式(Regular Expression)的开发手册(正则符号深入解析 )
【夯实技术基本功】「底层技术原理体系」全方位带你认识和透彻领悟正则表达式(Regular Expression)的开发手册(正则符号深入解析 )
86 0
|
7月前
|
前端开发 JavaScript
前端JavaScript入门-day08-正则表达式
前端JavaScript入门-day08-正则表达式
73 0
|
6月前
正则表达式(有关String当中,有关正则的方法)
正则表达式(有关String当中,有关正则的方法)
|
6月前
|
Python
python正则表达式入门
python正则表达式入门
|
6月前
|
数据采集 监控 Python
Python新手必看:正则表达式入门到精通只需这一篇!
了解 Python 中的正则表达式,用于高效处理字符串。导入 `re` 模块,用 `r` 前缀避免转义困扰。示例:`re.split` 切分字符串,`re.findall` 进行匹配与查找,数量词如 `*`, `+`, `?` 控制匹配次数,边界匹配定位开始或结束。使用 `group` 和 `sub` 进行组合操作,解决复杂文本处理问题。正则表达式是字符串处理的利器,助你轻松应对各种场景。
51 0
|
7月前
|
Linux Shell
Linux下的Shell基础——正则表达式入门(四)
Linux下的Shell基础——正则表达式入门(四)
50 1
Linux下的Shell基础——正则表达式入门(四)
|
7月前
|
Shell Linux Perl
Shell基础学习---3、Read读取控制台输入、函数、综合应用案例:归档文件、正则表达式入门(第二天学习)
Shell基础学习---3、Read读取控制台输入、函数、综合应用案例:归档文件、正则表达式入门
137 1
|
7月前
|
机器学习/深度学习 前端开发 JavaScript
正则表达式从入门到入坑
正则表达式从入门到入坑
|
7月前
|
Java 计算机视觉
【Java 正则表达式】简单用法,注意点,我学不会正则
【Java 正则表达式】简单用法,注意点,我学不会正则
|
7月前
|
人工智能 JavaScript 前端开发
正则表达式[入门]
正则表达式[入门]
53 0