正则,让我们开发so easy

306 阅读5分钟

正则表达式

在工作中似乎用到的正则不多,那可能是因为正则太过于抽象,不方便记忆和理解,导致大家对其使用不多,但是正则确实能够快速实现我们的功能,在工作中表单需要做很多校验,使用正则确实会为我们带来很多的便利

regular expression : RegExp

用来处理字符串的规则

  1. 只能处理字符串

  2. 一个规则: 可以验证字符串是否符合某个规则(test),也可以把字符串符合规则的内容捕获到(exec / match...)

创建方式

// 字面量创建 (两斜杠之间 就是描述规则的元字符)
let reg1 = /\d+/;
// 构造函数创建 (两个参数: 元字符字符串 修饰符字符串)
let reg2 = new RegExp("\\d+");
//区别
//1. 构造函数因为传递的是字符串,\需要写两个才代表斜杠
let reg = /\d+/g;
let reg1 = new RegExp("\\d+","g");
//2. 两斜杠之间 就是描述规则的元字符, 如果有变量,则需要构造函数方式
let type = "majun"
let reg2 = /^@"+type+"@$/
console.log(reg2.test("@majun@")) //false
console.log(reg2.test('@"""typeeee"@')) //true
let reg3 = new RegExp("^@"+type+"@$")
console.log(reg3.test("@majun@")) //true

正则表达式组成部分 (元字符 修饰符)

元字符
1. 量词元字符 设置出现的次数
*  0到多次
+  1到多次
?  01次
{n}  n次
{n,}  n到多次
{n,m}  n到m次

2. 特殊元字符 单个或组合在一起 代表特殊含义
\  转义字符(普通->特殊->普通)
.  除\n(换行符)以外的任意字符
^  开始
$  结束
\n  换行符
\d  0-9
\D  
\w  数字 字母 _
\W  
\s  空白字符(空格、制表符、换页符等)
\S  
\t  制表符 (一个TAB键:四个空格)
\b 边界
x|y  x或者y
[xyz] x或者y或者z
[^xy] 除了x y
[a-z] 指定a-z这个范围中的任意字符  [0-9a-zA-Z_] === \w
[^a-z]
()  分组
(?:) 只匹配不捕获
(?=) 正向预查
(?!) 负向预查

3. 普通元字符:代表本身含义
/mj/ 匹配的就是 "mj"
修饰符
1. i  ignoreCase 忽略大小写匹配
2. m  multiline  可以进行多行匹配
3. g  global  全局匹配

元字符详细解析

^ $

let reg = /^\d/
console.log(reg.test("mj"))  //false
console.log(reg.test("2021mj"))  //true

let reg1 = /\d$/
console.log(reg1.test("mj"))  //false
console.log(reg1.test("mj2021"))  //true

// 都不加 包含
let reg2 = /\d+/
// 都加 字符串只能是和规则一致的内容
let reg3 = /^\d+$/
console.log(reg3.test("1mj2"))  //false
console.log(reg3.test("12"))  //true

\

// . 代表除\n外的任意字符
let reg = /^2.3$/;
console.log(reg.test("2.3")); //true
console.log(reg.test("2@3")); //true
console.log(reg.test("23")); //false
// 基于转义字符  \.代表小数点
let reg1 = /^2\.3$/;
console.log(reg1.test("2.3")); //true
console.log(reg1.test("2@3")); //false
let str = "\\d";
let reg = /^\d$/;
console.log(reg.test(str)) //fasle
reg = /^\\d$/
console.log(reg.test(str)) //true

x|y

ler reg = /^18|29$/;
console.log(reg.test("18")) //true
console.log(reg.test("29")) //true
console.log(reg.test("189")) //true
console.log(reg.test("129")) //true
console.log(reg.test("1829")) //true
console.log(reg.test("829")) //true
console.log(reg.test("182")) //true
//直接x|y会存在很乱的优先级问题,一般配合小括号分组
reg = /^(18|29)$/;
// 18 29

[xyz]

// 中括号中出现的字符一般代表本身的含义
let reg = /^[@+]$/;
console.log(reg.test("@")) //true
console.log(reg.test("+")) //true
console.log(reg.test("@@")) //false
console.log(reg.test("@+")) //false

reg = /^[\d]$/;
console.log(reg.test("\\")) //false
console.log(reg.test("d")) //false
console.log(reg.test("9")) //true

reg = /^[\\d]$/;
console.log(reg.test("\\")) //true
console.log(reg.test("d")) //true
console.log(reg.test("9")) //false

// 中括号只有一位
reg = /^[18]$/;
console.log(reg.test("1")) //true
console.log(reg.test("8")) //true
console.log(reg.test("18")) //false

reg = /^[10-29]$/
// 1 或者 0-2 或者 9

常用的正则表达式

  1. 是否是有效数字
/*
 * 可能出现 + -, 也可能不出现
 * 一位0-9都可以,多为首位不能为0
 * 小数部分可能有可能没有,一旦有后面必须是小数点+数字
 */
reg = /^[+-]?(\d|([1-9]\d+))(\.\d+)?$/
  1. 验证密码
// 数字 字母 下划线
// 6-16位
let val = userPassInp.value
let reg = /\w{6,16}/
let flag = reg.test(val)

function checkpass () {
    if(val.length < 6 || val.length > 16){
        flag = false
        return 
    }
    let area = ['a','b',...,'_']
    for(let i=0;i<val.length;i++){
        if(!area.includes(val[i])){
            return flag = false
        }
    }
}
  1. 验证真实姓名
/*
 * 汉字 /^[\u4E00-\u9FA5]$/
 * 名字长度 2-10
 * 可能有译名 ·汉字 (·[\u4E00-\u9FA5]{2,10}){0,2}
 */
reg = /^[\u4E00-\u9FA5]{2,10}(.[\u4E00-\u9FA5]{2,10}){0,2}$/
  1. 验证邮箱
reg = /^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/

// \w+((-\w+)|(\.\w+))*
// 1. 开头是数字字母下划线(1到多次)
// 2. 还可以是 -数字字母下划线  或者 .数字字母下划线 整体0到多次
// 邮箱的名字由 数字 字母 下划线 - . 几部分组成,但是-/.不能连续出现且不能作为开始

// @[A-Za-z0-9]+
// 1. @后面紧跟着:数字 字母 (1到多次)

// ((\.|-)[A-Za-z0-9]+)*
// 1. 对@后面名字的补充

// \.[A-Za-z0-9]+
// 1. @xxx.com @xxx.cn 这个匹配的才是最后的域名(.com/.cn/.org/.edu/.net)

ma.jun@datatom.com
ma.jun191@iwhaleicloud.com
  1. 验证身份证
// 18位 最后一位可能为X
// 前六位 省市县  
// 中间八位 年月日 
// 最后四位 最后一位: X|\d  倒数第二位:偶数 女 奇数 男  其余经过算法算出来
let reg = /^\d{17}(\d|X)$/
//小括号分组的第二个作用:分组捕获
reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/
reg.exec('421127199504130075') //["421127199504130075", "421127", "1995", "04", "13", "7", "5", index: 0, input: "421127199504130075", groups: undefined]

正则的捕获

实现正则捕获的方法

  • 正则RegExp.prototype上的方法

    exec

    test

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

    replace

    match

    split

    ...

/*
* 基于exec实现的正则捕获
* 1. 捕获到的结果就是null或者一个数组
*   第一项:匹配到的内容
*   其余项:对应分组匹配到的内容
*   index: 匹配到内容的起始索引
*   input: 字符串原始值
* 2. 每次执行exec,只能捕获到一个符合正则规则的,都是第一个 (正则捕获的懒惰性)
*/
let str = "majun2021up2022up2023";
let reg = /\d+/;
console.log(reg.exec(str)) //['2021',index: 5, input: 'majun2021up2022up2023', groups: undefined]

//实现正则捕获的前提是:正则和字符串要匹配,不匹配返回null
let reg = /^\d+$/;
console.log(reg.test(str)) //false
console.log(reg.exec(str)) //null
// 懒惰性原因
// reg.lastIndex 正则下一次匹配的起始索引 默认为0
let str = "majun2021up2022up2023";
let reg = /\d+/;
console.log(reg.lastIndex) //0
console.log(reg.exec(str)) //['2021'...]
console.log(reg.lastIndex) //0

即使手动修改lastIndex也不起作用,我们可以加个修饰符g,浏览器默认会修改lastIndex,下一次从上一次结束的位置开始查找,所以可以得到后面内容

image.png

image.png

接下来实现一个execAll方法能够一次捕获到所有匹配内容

(function(){
    RegExp.prototype.execAll = function execAll (str) {
        // str 要匹配的字符串
        // this 正则实例
        // 防止没有加g 每次res都为第一项的数组 产生死循环
        if(!this.global) return this.exec(str) 
        let resultArr = [],
            res = this.exec(str);
        while (res) {
            // 只要捕获的内容不为null 继续捕获
            resultArr.push(res[0])
            res = this.exec(str);
        }
        return resultArr
    }
})()

字符串的match方法就可以捕获到所有内容

image.png

image.png

正则的分组捕获

(?:) 只匹配不捕获,像上面身份的捕获一样,因为\d|X 加了一个分组改变优先级,导致分组捕获到

let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/
console.log(reg.exec('421127199504130075')) //["421127199504130075", "421127", "1995", "04", "13", "7", "5", index: 0, input: "421127199504130075", groups: undefined]
console.log('421127199504130075'.match(reg)) //["421127199504130075", "421127", "1995", "04", "13", "7", "5", index: 0, input: "421127199504130075", groups: undefined]

let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(?:\d|X)$/
console.log(reg.exec('421127199504130075')) //["421127199504130075", "421127", "1995", "04", "13", "7", index: 0, input: "421127199504130075", groups: undefined]
console.log('421127199504130075'.match(reg)) //["421127199504130075", "421127", "1995", "04", "13", "7", index: 0, input: "421127199504130075", groups: undefined]

不设置g只匹配一次,exec和match获取的结果一样(既有大正则匹配的内容,也有小分组匹配的内容)

let str = "{0}年{1}月{2}日"
let reg = /\{(\d+)\}/
console.log(reg.exec(str)) //["{0}", "0", index: 0, input: "{0}年{1}月{2}日", groups: undefined]
console.log(str.match(reg)) //["{0}", "0", index: 0, input: "{0}年{1}月{2}日", groups: undefined]

设置g exec只能一次一个,但是match可以捕获所有,但是问题是没有捕获小分组内容

let str = "{0}年{1}月{2}日"
let reg = /\{(\d+)\}/g
console.log(reg.exec(str)) //["{0}", "0", index: 0, input: "{0}年{1}月{2}日", groups: undefined]
console.log(str.match(reg)) //["{0}", "{1}", "{2}"]

let aryBig = [],
    arySmall = [],
    res = reg.exec(str)
while (res) {
    let [big, small] = res
    aryBig.push(big)
    arySmall.push(small)
    res = reg.exec(str)
}

正则的分组引用

let str = "good"; // book foot seek look
let reg = /^[a-zA-Z]([a-zA-Z])\1[a-zA-Z]$/

正则捕获的贪婪性

贪婪性:正则捕获的时候,是按照当前正则匹配最长的结果来匹配的

let str = "majun2021up2022up2023"
let reg = /\d+/g
console.log(str.match(reg)) //["2021", "2022", "2023"]

reg = /\d+?/g
console.log(str.match(reg)) //["2", "0", "2", "1", "2", "0", "2", "2", "2", "0", "2", "3"]

做人不能像正则,又懒惰又贪婪

其他捕获方法

test

本意是匹配也能捕获,从字符串左侧开始匹配,匹配到符合正则规则的字符,返回true,否则返回false,同样的test在修饰符g下也会修改lastIndex的值

image.png

当test匹配到结尾或者匹配不到时,返回false,成功则向数组添加当前小分组匹配第一个元素内容 在RegExp的constructor中存在$1-$9,他们的具体指的是当前本次匹配小分组第一到第九捕获的内容

replace

字符串方法 替换 str.replace(str|reg, str|function)

let time = "2021-04-28"
let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/
time = time.replace(reg, "$1年$2月$3日")
console.log(time) //"2021年04月28日"

time = "2021-04-28"
time = time.replace(reg, (big,$1,$2,$3)=>{
    console.log(big,$1,$2,$3) //2021-04-28 2021 04 28
    return "$1年$2月$3日"
})
console.log(time) //"2021年04月28日"
// 单词首字母大写
let str = "good good study, day day up"
let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g
str = str.replace(reg, (...arg)=>{
    let [big, $1] = arg
    return  $1.toUpperCase() + big.substring(1)
})
// 字符串中哪个字母出现的次数最多,多少次?
let str = "abcgajgajjagjlagj"
let obj = {}
let str1 = [...str]
str1.forEach(item=>{
    if(obj.hasOwnProperty(item)) {
        obj[item]++
        return
    }
    obj[item] = 1
})
console.log(obj) //{a:5,b:1,c:1,g:4,j:5,l:1}
let max = -Infinty;
for(let key in obj){
    obj[key] > max ? max=obj[key] : null
}
let res = []
for(let key in obj){
    if(obj[key]===max){
        res.push(key)
    }
}
console.log(`出现最多的是${res}${max}次`)

// -----------------------
let str = "abcgajgajjagjlagj"
let str1 = [...str]
str1 = str1.sort((a,b)=>a.localeCompare(b)).join('')
//str1 = str1.sort().join('')
let reg = /([a-zA-Z])\1*/g
let ary = str1.match(reg).sort((a,b)=>b.length - a.length)
console.log(ary) //["aaaaa", "jjjjj", "gggg", "b", "c", "l"]
let max = ary[0].length
let res = [ary[0].substr(0,1)]
for(let i=1;i<ary.length;i++){
    if(ary[i].length < max) {
        break
    }
    res.push(ary[i].substr(0,1))
}
console.log(`出现最多的是${res}${max}次`)

// --------------------

let str = "abcgajgajjagjlagj"
let str1 = [...str]
str1 = str1.sort((a,b)=>a.localeCompare(b)).join('')
let max = 0,
    res = [],
    flag = false;
for(let i=str1.length;i>0;i--){
    let reg = new RegExp("([a-zA-Z])\\1{" + (i-1) + "}", "g")
    str1.replace(reg, (content,$1)=>{
        res.push($1)
        max=i
        flag = true
    })
    if(flag) break
}
console.log(`出现最多的是${res}${max}次`)

常用正则处理

~function () {
    function formatTime(template = "{0}年{1}月{2}日 {3}时{4}分{5}秒") {
        let ary = this.match(/\d+/g);
        template = template.replace(/\{(\d+)\}/g, (...arg)=>{
            let [, $1] = arg
            let time = ary[$1] || '00'
            // time = time.length < 2 ? '0' + time : time
            time.length < 2 ? time = '0' + time : null
            return time
        })
        return template
    }
    function queryURLParams() {
        let res = {}
        this.replace(/([^?=&#]+)=([^?=&#]+)/g, (...[, $1, $2])=>{
            res[$1] = $2
        })
        this.replace(/#([^?=&#]+)/g, (...[, $1])=>{
            res['hash'] = $1
        })
        return res
    }
    function millimeter() {
        return this.replace(/\d{1,3}(?=(\d{3})+$)/g, content=>{
            return content + ","
        })
    }
    ['formatTime','queryURLParams','millimeter'].forEach(item=>{
        String.prototype[item] = eval(item)
    })
}()
var a = "2021-4-28-15-25-1"
console.log(a.formatTime()) //2021年04月28日 15时25分01秒
console.log(a.formatTime("{0}/{1}/{2} {3}/{4}/{5}")) //2021/04/28 15/25/01
var b = "https://www.baidu.com?m=1&j=2#pathName"
console.log(b.queryURLParams()) //{m: "1", j: "2", hash: 29}
var c = "12345678"
console.log(c.millimeter()) //12,345,678