正则概念
RegExp 它是用于处理字符串的规则对象,用于匹配
(test)和捕获
(exec|match)。
注意,“ 匹配”得到的值为(true|false),而“ 捕获”得到的值是data。
正则组成
- 元字符:常用元字符、特殊元字符、普通元字符
- 修饰符:i m g
常用元字符
符号 | 语义 | 含义 |
---|---|---|
* | 0-N | 0 至 任意 |
+ | 1-N | 1 至 任意 |
? | 0-1 | 0 或者 1 |
{n} | N | 指定为 N 个 |
{n, m} | N-M | 指定为 N 至 M个 |
{n,} | N-任意 | 指定为 N 至 任意个 |
特殊元字符
符号 | 语义 | 含义 | ||
---|---|---|---|---|
\ | 转义 | 普通字符->特殊字符->普通字符串 | ||
. | 除 换行符 之外的任意普通字符 | 匹配 非\n之外的所有普通字符 | ||
^ | 起始字符 | 以哪个元字符为起始 | ||
$ | 结束字符 | 以哪个元字符为结束 | ||
\n | 换行符 | 换行 | ||
\d | 0-9 | 任意数字 | ||
\D | 非0-9 | 非任意数字 | ||
\s | 空白符 | 空格符、制表符、换页符等 | ||
\S | 非空白符 | 非空格符、制表符、换页符等 | ||
\w | 0-9 a-Z _ | 0-9 a-Z _ 中任意一个 | ||
\W | 非 0-9 a-Z _ | 不为 0-9 a-Z _中任意一个 | ||
\t | 制表符 | tab键:四个空格 | ||
或 | x | y 表示 x 或者 y 中任意一个字符 | ||
[] | 其中 | [xyz] 表示 x y z 中任意一个字符 | ||
[^] | 非其中 | 1 表示 非 x y z 中任意一个字符 | ||
[a-z] | 其中范围 | [a-z] 表示 a到z中任意一个字符,还可以0-9 a-Z | ||
2 | 非其中范围 | 2 表示非 a到z中任意一个字符 | ||
() | 分组 | 分组符号 | ||
(?:) | 只匹配不捕获 | |||
(?=) | 正向预查 | |||
(?!) | 负向预查 |
普通元字符
符号 | 语义 | 含义 |
---|---|---|
abcd | 字符串abcd | 常见的普通字符串 |
修饰符
console.dir(RegExp) // 去控制台看它的原型
符号 | 语义 | 含义 | 使用 |
---|---|---|---|
i | ignoreCase | 忽略大小写 | /T/i.test('test') // true |
m | multiline | 匹配多行 | /t/m.test('e \r\n t') // true |
g | global | 全局匹配 | /T/g.test('tes\r\nT') // true |
正则使用
两种创建正则表达式的方式
/1/img // /1/gim
new RegExp("1", "img") // /1/gim
/\d/img // /\d/img
new RegExp("\\d", "img") // /\d/gim
正则表达式中的元字符需要由字符串变量
拼接而成时,就不要使用字面量/1/img
的方式,可以使用new RegExp("1", 'img')
的方式。
let a = "abc", b = "efg"
// 元字符需要由`字符串变量`拼接而成的
new RegExp(a + b).test('abcefg') // true
// !不要使用这种方式哟
/ab/.test("abcefg") // false
匹配
/1/.test('1234567') // true
/[0-9]/.test('1234567') // true
/[a-z]/.test('abcd') // true
/./.test("\n") // false
/./.test('a')// true
/^19$/.test('19')// true
/^19$/.test('619')// false
/^19$/.test('196')// false
捕获
/1/.exec('123132') // ["1", index: 0, input: "123132", groups: undefined]
// 匹配身份证:前六位 省市县、中间八位年月日、最后四位:前两位是所在地公安局编码,倒数第二位 奇男偶女,倒数第一位是0-9 或者 x,x表示的是10。
let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|x)$/
reg.exec('421126199607171957')
// ["421126199607171957", "421126", "1996", "07", "17", "5", "7", index: 0, input: "421126199607171957", groups: undefined]
正则技巧
正则RegExp.prototype上的 test 和 exec 都能完成实现捕获。
字符串String.prototyp 上的 replace、match、split 也能实现捕获。
捕获的前提
正则和字符串要进行匹配,如果不匹配,捕获的结果就为null
let str = "test2019test2020test2021"
let reg = /\d+/
console.log(reg.lastIndex) // 0
console.log(reg.exec(str)) // ["2019", index: 4, input: "test2019test2020test2021", groups: undefined]
console.log(reg.lastIndex) // 0
// 基于exec实现的正则捕获
// 1. 捕获到的结果为null 或者为一个数组
// 2. 为数组时
// - 第一项: 本次捕获到的内容
// - 其余项: 对于小分组本次单独捕获的内容
// - index: 当前捕获内容在字符串中的起始索引
// - input: 原始字符串
// 3. 每次执行exec只能捕获一个符合正则规则的,那么默认情况下执行一百遍,获取到的结果都是一样的
// - 正则捕获的懒惰性:默认捕获第一个
// 4. reg.lastIndex 当前正则的下一次匹配的起始索引位置
// - 惰性捕获的原因:默认情况下lastIndex的值不会被修改,每一次都是从字符串的开始位置找,于是找到的永远只是第一个,默认情况下lastIndex是只读的,`无法修改`
reg = /\d+/g
console.log(reg.lastIndex)// 0
console.log(reg.exec(str))// ["2019", index: 4, input: "test2019test2020test2021", groups: undefined]
console.log(reg.lastIndex)// 8
console.log(reg.exec(str))// ["2020", index: 12, input: "test2019test2020test2021", groups: undefined]
console.log(reg.lastIndex)// 16
// 5. 使用全局匹配时,就能消除正则捕获的懒惰行了,所以解决方法就是全局匹配符·`g`
// - 全部都捕获到之后,再匹配会返回null,并且lastIndex还会重置为0。
// - 这就会形成了一个循环,再匹配就是重新开始匹配了。
实现一个匹配全部内容execAll
通过exec方法加全局匹配符g来实现execAll。
~function() {
function execAll (str) {
if (!this.global) {
console.error('未使用全局匹配符 g,只能匹配第一个')
return this.exec(str)
}
let result = []
let current = this.exec(str)
while (current) {
result.push(current[0])
current = this.exec(str)
}
return result.length ? null : result
}
RegExp.prototype.execAll = execAll
}();
let str = "test2019test2020test2021"
let reg = /\d+/g
reg.execAll(str) // ["2019", "2020", "2021"]
字符串的match方法等价楼上实现的execAll,并且execAll的实现也是模仿字符串的match来实现的,必须配合全局修饰符g
来使用。
let str = "test2019test2020test2021"
let reg = /\d+/g
str.match(reg) // ["2019", "2020", "2021"]
str.matchAll(reg) // 返回的是一个迭代器,迭代器中的每一次next()的结果是每一次reg.exec的结果,并且必须配合修饰符g来使用。
test的捕获
let str = '{2021}年{4}月{5}日'
let reg = /\{(\d+)\}/g
console.log(reg.test(str)) // true
console.log(RegExp.$1) // 2021
console.log(reg.test(str)) // true
console.log(RegExp.$1) // 4
console.log(reg.test(str)) // true
console.log(RegExp.$1) // 5
console.log(reg.test(str)) // false
console.log(RegExp.$1) // 5
console.log(reg.test(str)) // true
console.log(RegExp.$1) // 2021
test 捕获注意
和exec一样,test也能实现捕获,只不过它是通过Reg.Exp.$1等等特殊变量来获取每次匹配到的内容,$1
表示第一个分组中的内容,RegExp.$1 - RegExp.$9
获取当前本次正则匹配后,第一个到第九个分组的信息。
。如果没有分组的话,就如下图所示,通过$&
或者lastMatch
来获取匹配到的内容。
正则贪婪
正则又懒惰又贪婪,懒惰用g
,贪婪用?
正则匹配的贪婪性
使用
+
等量词 ,默认情况下,正则捕获的时候,获取的是按照当前正则所匹配的最长结果。
let str = "test2019test2020test2021"
let reg = /\d+/g
console.log(str.match(reg)) // ["2019", "2020", "2021"]
消除正则的贪婪性
在量词后面使用
?
,正则捕获时,获取的是按照当前正则所匹配的最短结果
let str = "test2019test2020test2021"
let reg = /\d+?/g
console.log(str.match(reg)) // ["2", "0", "1", "9", "2", "0", "2", "0", "2", "0", "2", "1"]
?在正则中的五种作用
- 问号左边是非量词元字符:本身代表量词元字符,左边的字符要出现 0 至 1 次
- 问号左边是量词元字符:取消捕获时候的贪婪性
- (?:):只匹配不捕获
- (?=):正向预查 (只匹配不捕获),必须条件符合
- (?!):负向预查 (只匹配不捕获),必须条件不符合
字符串的replace
全部替换
let str = "test2019test2020test2021"
let reg = /test/g
console.log(str.replace(reg, '测试')) // 测试2019测试2020测试2021
替换时使用反向引用
let str = "2021-04-05"
let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/
str.replace(reg, '$1年$2月$3日')
console.log(str.replace(reg, '$1年$2月$3日')) // 2021年04月05日
console.log(str.replace(reg, (...args) => {
console.log('args', args) // ["2021-04-05", "2021", "04", "05", 0, "2021-04-05"]
const [big , $1, $2, $3] = args
return $1 + '年' + $2 + '月' + $3 + '日'
})) // 2021年04月05日
// 看下面replace方法,实际上和exec的返回结果几乎一样
let str = "2021-04-05"
let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/
str.replace(reg, (...args) => {
console.log('args', args) // ["2021-04-05", "2021", "04", "05", 0, "2021-04-05"]
})
reg.exec(str) // ["2021-04-05", "2021", "04", "05", index: 0, input: "2021-04-05", groups: undefined]
首字符大写
let str = "what are you how are you who are you"
let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g
console.log(str.replace(reg, (...args) => {
let [big, $1] = args
$1 = $1.toUpperCase()
big = big.substring(1)
return $1+ big
})) // What Are You How Are You Who Are You
获取字符串中出现次数最多的字符
{
let str = "whatareyouhowareyouwhoareyou"
// 分割再排序和重组
str = str.split('').sort((a, b) => a.localeCompare(b)).join("") // "aaaaeeehhhooooorrrtuuuwwwyyy"
// 会匹配出现连续1次以上的字符,默认贪婪模式
let reg = /([a-zA-Z])\1+/g
let result = str.match(reg) // ["aaaa", "eee", "hhh", "ooooo", "rrr", "uuu", "www", "yyy"]
console.log(result.sort((a, b) => b.length - a.length)) // ["ooooo", "aaaa", "eee", "hhh", "rrr", "uuu", "www", "yyy"]
}
{
let str = "whatareyouhowareyouwhoareyou"
// 分割再排序和重组
str = str.split('').sort((a, b) => a.localeCompare(b)).join("") // "aaaaeeehhhooooorrrtuuuwwwyyy"
let max = 0,res = [], flag = false;
// 自定义排序,从最大找到最小
for (let i = str.length; i > 0; i --) {
let reg = new RegExp("([a-zA-Z])\\1{"+(i-1)+"}", 'g')
str.replace(reg, (content, $1) => {
res.push($1)
max = i
flag = true
})
if (flag) break;
}
console.log('出现次数最多的字符是 '+ res, '出现次数是' + max)
}
时间字符格式化
~function () {
// formatTime: 时间字符串的格式化
function formatTime (template) {
// 首先获取时间字符串中的年月日等信息
let timeAry = this.match(/\d+/g)
template = template || "{0}年{1}月{2}日 {3}时{4}分{5}秒"
template = template.replace(/\{(\d+)\}/g, (content, $1) => {
let time = timeAry[$1] || "00"
time.length < 2 ? time = "0" + time : null
return time
})
return template
}
function queryURLParams () {}
// 扩展到内置类 String.prototype上,这种方式是循环往原型上扩展方法
["formatTime", "queryURLParams"].forEach(item => {
// String.prototype["formatTime"] = eval("formatTime");
String.prototype[item] = eval(item);
})
}()
let str = "2021-04-05 23:36:2"
console.log(str.formatTime()) // 2021年04月05日 23时36分02秒
console.log(str.formatTime("{1}月{2}日")) // 04月05日
console.log(str.formatTime("{1}/{2} {3}:{4}")) // 04/05 23:36
URL参数格式化
~function () {
function formatTime (template) {
}
// URL参数格式化
function queryURLParams () {
let obj = {}
this.replace(/([^?=&#]+)=([^?=&#]+)/g, (...[, $1, $2]) => {
obj[$1] = $2
})
this.replace(/#([^?=&#]+)/g, (...[, $1]) => {
obj['HASH'] = $1
})
return obj
}
// 扩展到内置类 String.prototype上,这种方式是循环往原型上扩展方法
["formatTime", "queryURLParams"].forEach(item => {
// String.prototype["formatTime"] = eval("formatTime");
String.prototype[item] = eval(item);
})
}()
let str = "http://www.hao6.website:888/?id=999&name=zs#video"
console.log(str.queryURLParams()) // {id: "999", name: "zs", HASH: "video"}
大数字千分位格式化
~function () {
function formatTime (template) {
}
function queryURLParams () {
}
function millimeter () {
let result = ''
result = this.replace(/\d{1,3}(?=(\d{3})+$)/g, content => {
return content + ','
})
return result
}
// 扩展到内置类 String.prototype上,这种方式是循环往原型上扩展方法
["formatTime", "queryURLParams", "millimeter"].forEach(item => {
// String.prototype["formatTime"] = eval("formatTime");
String.prototype[item] = eval(item);
})
}()
let str = "1234193949"
console.log(str.millimeter()) // 1,234,193,949
爬虫
通过网络请求获取你想要的内容字符串,然后通过以上讲述的正则技巧来捕获你需要的那内容,之后保存起来,需要用的时候,再读取、运算,最后经过自定义的筛选,最终渲染出你想要的结果。