正则表达式

正则表达式是描述一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。

我们判断字符串是否含有子串,我们一般会用到indexOf 还有includes方法, 比如 "abc".includes("ab") 或者 "abc".indexOf(ab), 那么这两个有什么区别?

includes 返回的是Boolean值;

而indexOf 返回的是子串在字符串首次出现的下标,若没有出现返回-1

除了以上两种方法,我们还有正则表达式,/ab/.test("abc")

先来看几个常用特殊的字符

  1. * 代表前面的字符0次多次, 例子 /ac*/.test("abc")
说明

ac能匹配“a”,也能匹配“ac”以及“acc”。等价于{0,}。

  1. + 代表前面的字符1次多次/ac+/.test("abc")
说明

ac+ 能匹配“ac”以及“acc”, 但不能匹配”a”, +等价于{1,}。

  1. ? 代表前面的字符0次1次, 例子 /ac?/.test("abc")
    例如,“do(es)?”可以匹配“do”或“does”。?等价于{0,1}。
  2. /d 匹配数字
  3. \w 匹配字母、数字、下划线。等价于 [A-Za-z0-9_]
  4. \s 匹配所有空白符,包括换行
  5. \S 匹配非空白符,不包括换行。
  6. [\s\S] 匹配所有

基础

字符组 []

字符组:允许匹配一组可能出现的字符。比如 [Jj], 那么既可以匹配J,也能匹配j, 例子
/[Jj]ava[Ss]cript/.test("JavaScript")

区间

在字符组中用-代表区间,即[0-9]表示的是[0123456789], 同理[a-z]表示的是a到z任意的数字

字符转义

上面所说,[]表示字符组, 而- 表示区间连接符,那么如果要匹配[] 那么就需要使用转义符 \

答案

/\[\]/.test("[]")

取反 [^]

true 取反 就是false, 在正则这里的取反是指 不会出现的字符

比如: 匹配不包含数字的字符串 /[^0-9]/.test("abc"),

注意:^符号要在中括号内,不然^代表的是匹配以^后跟随的的字符开头的字符串/^gg/.test("gg = good game"), 这里表示的是以gg开头的匹配式。

这里顺带说一下 $, 表示以$前面的字符组做结尾的匹配式 /world$/.test("hello world")

不含小写字母的数据
`/[^a-z]/.test("0123123ADFADJFKL")`

重复

在一个字符组后加上{N} 就可以表示在它之前的字符组出现N次。例如 /\d{3}/ ,表示数字重复3次

假设要匹配 电话号码,那么应该怎么做, 电话号码的格式是020-12345678

匹配电话号码
`/\d{3}-\d{8}/`
重复区间 {M,N}

可能有时候,我们不知道具体要匹配字符组要重复的次数,比如身份证有15位也有18位的。那么这时候就可以用重复区间,

语法:{M,N},M是下界而N是上界。

练习 匹配所有的手机号


我们知道手机号必须为11位数,并符合下列几个规则:

第一位数字必须以1开头,第二位数字可以是[3,4,5,7,8]中的任意一个,后面9个数是[0-9]中的任意一个数字
匹配所有的手机号
`/1[34578]\d{9}/`

贪婪匹配与非贪婪匹配

贪婪模式:在整个表达式匹配成功的前提下,尽可能多的匹配 (*);

非贪婪模式:在整个表达式匹配成功的前提下,尽可能少的匹配 (?);

例子: <div>12312</div><div>hello world</div>

贪婪匹配: "<div>12312</div><div>hello world</div>".match(/<div>.*</div>/), 会匹配到 <div>12312</div><div>hello world</div>

非贪婪匹配: "<div>12312</div><div>hello world</div>".match(/<div>.*?</div>/), 会匹配到 <div>12312</div>, 这是因为非贪婪匹配,匹配到第一个子串就结束了,不再往后匹配了

分组

分组:使用的是(),分组一般用在提取匹配的字符串的子串。 举个例子: 020-12345678 这是我们的电话号码,但我想提取他的区号真实的电话号码,那么我们只需要/(\d{3})-(\d{8})/即可

练习1: 如果我想要提取<div>hello world</div>

答案
"<div>hello world</div>".match(/<div>(.*?)<\/div>/)

比较:
1. <div>.*?</div>
2. <div>(.*?)</div>

练习2: 如果 我们拥有日期的格式为 2020-05-20 或者 2020 05 20 或者 2020/05/20 这样的格式,我们想要获取他的年月日 应该怎么写

答案 /(\d{4})[\-\s\/](\d{2})[\-\s\/](\d{2})/
测试一下

"2020-05-20".match(/(\d{4})[\-\s\/](\d{2})[\-\s\/](\d{2})/)

中间分隔符是[\-\s\/], 我们换成[\s\S]是否也可以?

那如果日期的格式是 2020-5-20 或者是 2021-2-2 那应该怎么改造他

答案 /(\d{4})[-](\d{1,2})[-](\d{1,2})/

非捕获分组

有时候,我们并不需要捕获某个分组的内容,但是又想使用分组的特性。即我不要这个分组。
非捕获分组: (?:表达式)

例子: 现在有 电话号码 020-12345678 或者tel:12345678, 我只要要他的电话号码,那么(?:\d{3}|tel)[-:](\d{8})

分组回溯引用

分组回溯引用的意思就是,我能使用之前匹配的分组, 用\1表示第一个分组, \2表示第二个分组,如此类推

例如,要匹配一段 HTML 代码,比如:<font>hello world</font>,我们会写成 /<\w+>.*?<\/\w+>/, 这样能匹配<font>hello world</font>, 但是如果数据改成这样:<font>hello world</bar>, fontbar 不是一对正常的标签,所以上面的表达式不太正确, 这个时候就可以使用分组回溯引用, /<(\w+)>.*?<\/\1>/

练习: 如果要匹配符合 ab ba 这种关系的单词, 应该怎么写?

匹配abba, asffs

答案 (\w+)(\w+)\2\1

正向先行断言

正向先行断言(?=表达式),指在某个位置向右看,表示所在位置右侧必须能匹配表达式,但匹配的表达式不会出现在结果组里面

例如:

我喜欢你 我喜欢 我喜欢我 喜欢 喜欢你

如果要取出喜欢两个字,要求这个喜欢后面有你,这个时候就要这么写:喜欢(?=你),这就是正向先行断言。

看这段正则表达式(?=.*?[a-z])(?=.*?[A-Z]).+, 这段正则能够匹配包含至少一个大小写字母的字符串, 把正则拆开就是

  • (?=) 说明是后面必须是什么东西
  • .*? 表示 任意东西
  • [a-z] 表示 a-z 的小写字母

结合就是 必须包含a到z的任意字符串

练习: 密码强度验证规则如下:

  • 至少一个大写字母
  • 至少一个小写字母
  • 至少一个数字
  • 至少8个字符
答案 /(?=.*?[a-z])(?=.*?[A-Z])(?=.*?[0-9]).{8}/

反向先行断言

反向先行断言(?!表达式)的作用是保证右边不能出现某字符。

用上面的例子就是,如果要取出喜欢两个字,要求这个喜欢后面没有你,这个时候就要这么写:喜欢(?!你),这就是反向先行断言。

我觉得:正向先行断言跟反向先行断言基本类似,只是一个取反的操作

练习: 排除qq邮箱

答案 /@(?!qq).*/
@后面没有qq

用正向现行断言以及反向现行断言 千分位格式化数字

"100000".replace(/\B(?=(\d{3})+(?!\d))/g, ',')

解析: /\B(?=(\d{3})+(?!\d))

  • \B, 首先先理解 \b, 在字符串中 "here is a word", 在这个字符串中其实用很多\b, 就是单词与单词之间有\b,所以如果用 /\bhere\b/ 可以匹配到here这个单词,但如果字符串是hereisaword, 这样就匹配不了here了,我们可以简单是想象成空格符
    \B是只非字间, 和 \d\D, \w\W 的取反关系一样
  • (?=(\d{3})): 先分组,3个数字为一组,加正向先行断言也就是 必须包含3个数字为一组的字符串
  • + 一次或多次
  • (?!\d): 反向先行断言,后面必须不能包含数字
  • 结合就是 3个数字为一组的子串后面必须没有数字

正向后行断言

先行断言和后行断言只有一个区别,即先行断言从左往右看,后行断言从右往左看。

正向后行断言(?<=表达式),指在某个位置向左看,表示所在位置左侧必须能匹配表达式

例如:如果要取出喜欢两个字,要求喜欢的前面有我,后面有你,这个时候就要这么写:(?<=我)喜欢(?=你)

反向后行断言

反向后行断言(?<!表达式),指在某个位置向左看,表示所在位置左侧不能匹配表达式

用上面的例子就是, 我喜欢你, 喜欢前面没有我

例子: 匹配一个 $ 符号: (?<!\$)\$[^\$]*\$(?!.)

解析:

  • (?<!\$)\$: $ 前面没有$
  • \$(?!$): $ 的后面没有$
  • [^\$]*: 除了$ 的任意字符

应用

  • 日期转化, 获取年月日,时分秒
  • 获取url中的参数
  • trim() 函数
  • 模板
function getUrlParams(url) {
    const pattern = new RegExp(/(\w+)=(\w+)/, 'gi')
    let res = {};
    str.replace(reg, (match, p1, p2) => {
        res[p1] = p2;
        return `${p1}=${p2}`
    })
    return res;
}

getUrlParams("https://www.baidu.com?username=admin&password=88888888&code=1234")

trim()

function trim(str, type: "left" | "right" | "both" | "all") {
    if (type === 'left') {
        return str.replace(/(^\s*)/g, "");
    }
    if (type === 'right') {
        return str.replace(/(\s*$)/g, "")
    }
    if (type === 'both') {
        return str.replace(/(^\s*)|(\s*$)/g, ""); 
    }
    return str.replace(/(\s*)/g, "");
}

简易模板引擎


const data = {name: 'Bill', age: 111};
const template = `
    

my name is {{name}}

年龄 {{age}}

` function generate(template, mapData) { const reg = new RegExp(/\{\{(.*?)\}\}/, "g"); const res = template.replace(reg, (match, p1) => mapData[p1]) return res; } const a = generate(template, data); document.body.innerHTML += a

…完