JavaScript正则快速入门

984 阅读6分钟
  • 正则表达式(Regular Expression)是用于匹配字符串中字符组合的模式。在 JavaScript中,正则表达式也是对象

  • 通常用来查找、替换那些符合正则表达式的文本,许多语言都支持正则表达式

  • 作用:表单验证(匹配)、 过滤敏感词(替换)、 字符串中提取我们想要的部分(提取

对于前端程序员来说,如若精力有限,对于正则可以只做到看得懂、能写基础的正则判断即可,各类正则插件可以辅助我们开发。当然,本文基本上覆盖了正则的常规用法

语法

JavaScript 中定义正则表达式的语法有两种:字面量以及构造函数

  • 字面量方式定义正则表达式:

    const 变量名 = /表达式/
    

    其中,/ /是正则表达式字面量,正则表达式也是对象

  • 构造函数 (RegExp) 创建(不常用):

    const regobj = new RegExp('表达式')
    

正则检验最常用方法:

  • 判断是否有符合规则的字符串:

    • test() 方法,用来查看正则表达式与指定的字符串是否匹配
    • 如果正则表达式与指定的字符串匹配 ,返回true,否则false
  • 语法:

    regobj.test(被检测的字符串)
    
  • 检索(查找)符合规则的字符串:

    • exec() 方法 在一个指定字符串中执行一个搜索匹配
    • 如果匹配成功,exec() 方法返回一个数组,否则返回null
  • 语法:

    regobj.exec(被检测的字符串)
    

正则表达式检测查找test方法和exec方法有什么区别?

  1. test方法用于判断是否有符合规则的字符串,返回的是布尔值 找到返回 true,否则 false
  2. exec方法用于检索(查找)符合规则的字符串,找到返回数组,否则为 null

元字符

  • 普通字符:大多数的字符仅能够描述它们本身,这些字符称作普通字符。例如所有的字母和数字,也就是说普通字符只能够匹配字符串中与它们相同的字符
  • 元字符一些具有特殊含义的字符,可以极大提高了灵活性和强大的匹配功能
    • 比如,规定用户只能输入英文26个英文字母,普通字符的话要写很长一段: abcdefg…
    • 但是,换成元字符写法:[a-z],十分简洁
  • 我们可以把元字符简单分为三类:
    • 边界符(表示位置,开头和结尾,必须用什么开头,用什么结尾)
    • 量词(表示重复次数)
    • 字符类(比如 \d 表示 0~9
  • 注:如要匹配特殊字符本身,需要在前加转义字符\进行转义

边界符

  • 正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符

    边界符说明
    ^表示匹配行首的文本(以谁开始)
    $表示匹配行尾的文本(以谁结束)

    如果 ^ 和 $ 在一起,表示精确匹配

    // 边界符
    console.log(/^哈/.test('哈哈')) // true
    console.log(/^哈/.test('二哈')) // flase
    console.log(/^哈$/.test('哈')) // true  精确匹配,只有这种情况为true 否则全是false
    console.log(/^哈$/.test('哈哈')) // false
    console.log(/^哈$/.test('二哈')) // false
    
    //易错点
    let firstString = "Ricky is first and can be found."
    let firstRegex = /^Ricky/ //这里的^指的应该是^后的字符串,而不是单个字符
    firstRegex.test(firstString) //true
    let notFirst = "You can't find Ricky now."
    firstRegex.test(notFirst) //false
    

量词

  • 量词用来设定某个模式出现的次数

  • 指的是量词最近的一个字符

  • 使用精确匹配

    量词说明
    *重复零次或更多次
    +重复一次或更多次
    ?重复零次或一次
    {n}重复n次
    {n,}重复n次或更多次
    {n,m}重复n到m次

    逗号左右两侧千万不要出现空格

    //  量词 * 类似 >=0 次
    console.log(/^哈*$/.test('')) // true
    console.log(/^哈*$/.test('哈')) // true
    console.log(/^哈*$/.test('哈哈')) // true
    console.log(/^哈*$/.test('哈很哈')) // false,精确匹配,只能为'哈'
    
    //  量词 + 类似 >=1 次
    console.log(/^哈+$/.test('')) // false
    console.log(/^哈+$/.test('哈')) // true
    console.log(/^哈+$/.test('哈哈')) // true
    console.log(/^哈+$/.test('哈很哈')) // false,精确匹配,只能为哈
    
    //  量词 ? 类似  0 || 1
    console.log(/^哈?$/.test('')) // true
    console.log(/^哈?$/.test('哈')) // true
    console.log(/^哈?$/.test('哈哈')) // false
    console.log(/^哈?$/.test('哈很哈')) // false,精确匹配,只能为哈,且数量为1或者0
    
    // 量词 {n} 写几,就必须出现几次
    console.log(/^哈{4}$/.test('哈哈哈')) //false
    console.log(/^哈{4}$/.test('哈哈哈哈')) //true
    console.log(/^哈{4}$/.test('哈哈哈哈哈')) //false
    
    // 量词 {n,}   >=n
    console.log(/^哈{4,}$/.test('哈哈哈')) //false
    console.log(/^哈{4,}$/.test('哈哈哈哈')) //true
    console.log(/^哈{4,}$/.test('哈哈哈哈哈')) //true
    
    // 量词 {n,m}  逗号左右两侧千万不能有空格   >=n && <= m
    console.log(/^哈{4,5}$/.test('哈哈哈')) //false
    console.log(/^哈{4,5}$/.test('哈哈哈哈')) //true
    console.log(/^哈{4,5}$/.test('哈哈哈哈哈')) //true
    console.log(/^哈{4,5}$/.test('哈哈哈哈哈哈')) //false
    

字符类

中括号

从前往后匹配,若是全局匹配,则匹配完满足项后,会从满足项下一个开始匹配

  • [ ] 匹配字符集合

    • 例如:
      • [abc] 匹配abc其中的任何单个字符
  • [ ] 里面加上 - 连字符:使用连字符 - 表示一个范围

    • 例如:
      • [a-z] 表示 a 到 z,26个英文字母都可以
      • [a-zA-Z] 表示大小写都可以
      • [0-9] 表示 0~9 的数字都可以
  • [ ] 里面加上 ^ 取反符号:表示不在中括号内的字符

    • 例如:
      • [^a-z] 匹配除了小写字母以外的字符

拓展:

. 匹配除换行符(\n、\r)之外的任何单个字符,相等于 [^\n\r],的任何单个字符

[\u4e00-\u9fa5]:查找一个汉字

逻辑或

正则表达式使用 | 表示逻辑或, 使用 | 时,用 () 将正则分组

符合其中一个或以上就可以匹配

捕获组

捕获组意义:

  • /oh+/ 该正则表达式表示一个字母 o 后面跟上一个或者多个字母 h ,用于匹配 oh 或者 ohhhhh。但此时我们想将 + 运用于 oh 这个整体,办法就是给 oh 加括号:/(oh)+/,加入括号后就形成了一个捕获组

捕获组的作用:

  • 匹配子表达式内容,匹配结果以编号或显示命名的方式存在内存,可供正则本身,也可供替换使用

语法:

  • 数字编号(pattern),匹配结果保存为数字
  • 显示命名(?<name>pattern),匹配结果保存到变量name中
  • 非捕获(?:pattern),标识不需要保存的组

捕获组重用模式

  • 分组匹配的子字符串被保存到一个临时的“变量”, 可以使用同一正则表达式和反斜线及捕获组的编号来访问它(例如:\1)。 捕获组按其开头括号的位置自动编号(从左到右),从 1 开始

    //下面的示例是匹配被空格隔开的两个相同单词
    const repeatStr = 'row row row oh row row'
    const repeatRegex = /(\w+) \1 \1/
    console.log(repeatRegex.test(repeatStr)) //true
    console.log(repeatStr.match(repeatRegex)) //["row row row", "row"]
    //如果使用 g 标志,则将返回与完整正则表达式匹配的所有结果,但不会返回捕获组
    //如果未使用 g 标志,则仅返回第一个完整匹配及其相关的捕获组(Array)。在这种情况下,返回的项目将具有如下所述的其他属性
    
    //匹配三个相同数字
    const repeatNum = "42 42 42"
    const reRegex = /^(\d+) \1 \1$/
    const result = reRegex.test(repeatNum)
    
  • 捕获组搜索和替换:

    // 隐藏手机号中间四位
    const tel = '13611112222'  
    console.log(tel.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')) //136****2222
    
    //交换位置
    "Code Camp".replace(/(\w+)\s(\w+)/, '$2 $1') //Camp Code
    

    在replace中:$1 对应第一个小括号内容,$2 对应第二个括号内容,依次类推(捕获组),可以搭配()将字符串进行分割

(?:)非捕获型分组 (non capturing group)

  • 有的时候我们可能只想匹配分组,但是并不想缓存(不想捕获)匹配到的结果,就可以在我们的分组模式前面加上?:

    let reg = /(?:\d{2}).(\d{2}).(\d{4})/
    let originString = '06-18-2023'
    reg.test(originString) // true
    originString.match(reg) // ['06-18-2023', '18', '2023']
    

    match返回的是捕获组,还有其他属性,本文为了方便查看只截取前面的匹配数据部分

    由此我们可知,加入?:正则表达式依然是匹配的,但是 $1 不是 06,而是 18,因为我们在第一个括号里加了?:,06 就不会被捕获。match()的执行结果会受?:的影响

预定义

  • 预定义:指的是某些常见模式的简写方式(短语元字符 shorthand character classes)

    预定义类说明
    \d匹配0-9之间的任一数字,相当于[0-9]
    \D匹配所有0-9以外的字符,相当于[^0-9]
    \w匹配任意的字母、数字和下划线,相当于[A-Za-zO-9 ]
    \W除所有字母、数字和下划线以外的字符,相当于[^A-Za-zO-9 ]
    \s匹配空格(包括制表、回车、换行、垂直制表、换页、空格等),相当于[ \t\r\n\v\f]
    \S匹配非空格的字符,相当于[^ \t\r\n\v\f]

匹配模式

贪婪匹配

  • 在正则表达式中,贪婪(greedy)匹配会匹配到符合正则表达式匹配模式的字符串的最长可能部分,并将其作为匹配项返回
  • 例如:
    • 可以将正则表达式 /t[a-z]*i/ 应用于字符串 "titanic"。 这个正则表达式是一个以 t 开始,以 i 结束,并且中间有一些字母的匹配模式
    • 正则表达式默认是贪婪匹配,因此匹配返回为 ["titani"]。 它会匹配到适合该匹配模式的最大子字符串

惰性匹配

  • 除了贪婪匹配,另一种方案称为懒惰(lazy)匹配,它会匹配到满足正则表达式的字符串的最小可能部分

  • 你可以使用 ? 字符来将其变成懒惰匹配 (放在量词后)。 调整后的正则表达式 /t[a-z]*?i/ 匹配字符串 "titanic" 返回 ["ti"]

    //匹配标签
    let text = "<h1>Winter is coming</h1>"
    let myRegex = /<.*?>/g
    let result = text.match(myRegex) //返回['<h1>', '</h1>']
    //匹配字符
    const str = 'helloooo world'
    const reg = /o+?/
    console.log(str.match(reg)) // 'o'
    const reg1 = /o+/
    console.log(str.match(reg1)) // 'oooo'
    

实际使用示例:

  • 用户名只能是数字与字母

  • 用户名中的数字必须在最后,数字可以有零个或多个,用户名不能以数字开头

  • 用户名字母可以是小写字母和大写字母

  • 用户名长度必须至少为两个字符。 两位用户名只能使用字母

    let username = "JackOfAllTrades"
    let userCheck = /^([a-z]{2,}|[a-z]+\d{2,})(\d*)$/i // 修改这一行
    let result = userCheck.test(username)
    

断言

先了解下以下几个概念

  • 零宽:只匹配位置,在匹配过程中,不占用字符,所以被称为零宽
  • 先行:正则引擎在扫描字符的时候,从左往右扫描,匹配扫描指针未扫描过的字符,先于指针,故称先行
  • 后行:匹配指针已扫描过的字符,后于指针到达该字符,故称后行,即产生回溯
  • 正向:即匹配括号中的表达式
  • 负向:不匹配括号中的表达式

断言只匹配某些位置,在匹配过程中,不占用字符,因此又被称为 "零宽"

正向先行断言和负向先行断言
  • 先行断言 (Lookaheads)是告诉 JavaScript 在字符串中向前查找的匹配模式(依旧是从左到右扫描)。 当想要在同一个字符串上搜寻多个匹配模式时,这可能非常有用

  • 有两种先行断言:正向先行断言(positive lookahead)和负向先行断言(negative lookahead)

  • 正向先行断言会查看并确保搜索匹配模式中的元素存在,但实际上并不匹配(意思是必须有,使用test可以检测,但使用match不会匹配到)。 正向先行断言的用法是 (?=...),其中 ... 就是需要存在但不会被匹配的部分

  • 负向先行断言会查看并确保搜索匹配模式中的元素不存在。 负向先行断言的用法是 (?!...),其中 ... 是希望不存在的匹配模式。 如果负向先行断言部分不存在,将返回匹配模式的其余部分

  • 使用示例:

    let str = 'au bt'     
    let str1Regex = /[a-zA-Z](?=u)/
    let str2Regex = /[a-zA-Z](?!u)/
    console.log(str.match(str1Regex)) // ['a'],从左到右扫描,匹配第一个 u 前面的字母
    console.log(str.match(str2Regex)) // ['u'],从左到右扫描,匹配第一个非 u 前面的字母
    
    //先行断言的更实际用途是检查一个字符串中的两个或更多匹配模式
    //这里有一个简单的密码检查器,密码规则是 3 到 6 个字符且至少包含一个数字
    let password = "abc123"
    let checkPass = /(?=\w{3,6})(?=\D*\d)/
    checkPass.test(password)
    
    //在正则表达式 pwRegex 中使用先行断言以匹配大于 5 个字符且有两个连续数字的密码
    let sampleWord = 'astronaut'
    let pwRegex = /(?=\w{5,})(?=\D+\d{2})/ // 修改这一行
    let result = pwRegex.test(sampleWord)
    
正向后行断言和负向后行断言
  • 与正向先行断言和负向先行断言正好相反

    let str = 'ua bt'
    let str1Regex = /(?<=u)[a-zA-Z]/
    let str2Regex = /(?<!u)[a-zA-Z]/
    console.log(str.match(str1Regex)) // ['a'],从左到右扫描,匹配第一个 u 后面的字母
    console.log(str.match(str2Regex)) // ['u'],从左到右扫描,匹配第一个非 u 后面的字母
    

修饰符

  • 修饰符约束正则执行的某些细节行为,如是否区分大小写、是否支持多行匹配等

  • 语法:

    /表达式/修饰符
    
    • i 是单词 ignore 的缩写,正则匹配时字母不区分大小写
    • g 是单词 global 的缩写,匹配所有满足正则表达式的结果
    • m 是单词 multi line 的缩写,只有使用 ^$才起作用
    console.log(/^java$/.test('java')) //true
    console.log(/^java$/i.test('JAVA')) //true
    const str1 = 'hello world,HELLO WORLD'
    const re = str1.replace(/world/ig, 'JavaScript') //hello JavaScript,HELLO JavaScript
    const str2 = 'hello\n2world\n3hellow\n4JavaScript'
    console.log(str2.match(/^\d/m)) //2
    console.log(str2.match(/^\d/gm)) //2 3 4