-
正则表达式(Regular Expression)是用于匹配字符串中字符组合的模式。在 JavaScript中,正则表达式也是对象
-
通常用来查找、替换那些符合正则表达式的文本,许多语言都支持正则表达式
-
作用:表单验证(匹配)、 过滤敏感词(替换)、 字符串中提取我们想要的部分(提取)
对于前端程序员来说,如若精力有限,对于正则可以只做到看得懂、能写基础的正则判断即可,各类正则插件可以辅助我们开发。当然,本文基本上覆盖了正则的常规用法
语法
JavaScript 中定义正则表达式的语法有两种:字面量以及构造函数
-
字面量方式定义正则表达式:
const 变量名 = /表达式/
其中,
/ /
是正则表达式字面量,正则表达式也是对象 -
构造函数 (RegExp) 创建(不常用):
const regobj = new RegExp('表达式')
正则检验最常用方法:
-
判断是否有符合规则的字符串:
test()
方法,用来查看正则表达式与指定的字符串是否匹配- 如果正则表达式与指定的字符串匹配 ,返回
true
,否则false
-
语法:
regobj.test(被检测的字符串)
-
检索(查找)符合规则的字符串:
exec()
方法 在一个指定字符串中执行一个搜索匹配- 如果匹配成功,
exec()
方法返回一个数组,否则返回null
-
语法:
regobj.exec(被检测的字符串)
正则表达式检测查找test方法和exec方法有什么区别?
test方法
用于判断是否有符合规则的字符串,返回的是布尔值 找到返回 true,否则 falseexec方法
用于检索(查找)符合规则的字符串,找到返回数组,否则为 null
元字符
- 普通字符:大多数的字符仅能够描述它们本身,这些字符称作普通字符。例如所有的字母和数字,也就是说普通字符只能够匹配字符串中与它们相同的字符
- 元字符:一些具有特殊含义的字符,可以极大提高了灵活性和强大的匹配功能
- 比如,规定用户只能输入英文26个英文字母,普通字符的话要写很长一段:
abcdefg…
- 但是,换成元字符写法:
[a-z]
,十分简洁
- 比如,规定用户只能输入英文26个英文字母,普通字符的话要写很长一段:
- 我们可以把元字符简单分为三类:
- 边界符(表示位置,开头和结尾,必须用什么开头,用什么结尾)
- 量词(表示重复次数)
- 字符类(比如
\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