正则表达式
正则表达式是描述一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。
我们判断字符串是否含有子串,我们一般会用到indexOf
还有includes
方法, 比如 "abc".includes("ab")
或者 "abc".indexOf(ab)
, 那么这两个有什么区别?
includes 返回的是Boolean值;
而indexOf 返回的是子串在字符串首次出现的下标,若没有出现返回-1
除了以上两种方法,我们还有正则表达式,/ab/.test("abc")
先来看几个常用特殊的字符
*
代表前面的字符0次
或多次
, 例子/ac*/.test("abc")
说明
ac能匹配“a”,也能匹配“ac”以及“acc”。等价于{0,}。
+
代表前面的字符1次
或多次
,/ac+/.test("abc")
说明
ac+ 能匹配“ac”以及“acc”, 但不能匹配”a”, +等价于{1,}。
?
代表前面的字符0次
或1次
, 例子/ac?/.test("abc")
例如,“do(es)?”可以匹配“do”或“does”。?等价于{0,1}。/d
匹配数字\w
匹配字母、数字、下划线。等价于 [A-Za-z0-9_]\s
匹配所有空白符,包括换行\S
匹配非空白符,不包括换行。[\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>
, font
和 bar
不是一对正常的标签,所以上面的表达式不太正确, 这个时候就可以使用分组回溯引用, /<(\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
…完