什么是RegExp(正则表达式)
RegExp叫做正则表达式,是用于匹配字符串中字符组合的模式,更贴切的说是用自己指定的规则去检测或者修改字符串。
例如,我们想要去判断某个字符串中是否拥有某个字符,可以通过遍历这个字符串去查找,也可以通过String.prototyp上的includes、indexOf或者lastIndexOf去判断,学会了正则之后就又多了一个办法;并且还可以实现精准匹配,不仅仅局限于匹配单个字符,可以进行整个字符串匹配,这不就是你想要的吗?
怎么学习正则呢?需要先了解正则的组成:元字符+修饰符=正则表达式
创建方式
正则表达式有两种创建的方式:字面量和构造函数。
- 字面量方式
literal:/test/i,用两个/包裹起来,里面的就是正则的规则; - 构造函数方式:
new RegExp(rule | literal, modifier):new RegExp('test', 'i'),new RegExp(/test/, 'i')。 上面三个方式创建的正则表达式是一样的效果,构造函数方式通常用于需要使用变量来作为规则时使用。
正则的组成
元字符
元字符是正则匹配一切的源头,一切的规则都需要元字符来实现匹配,下面详细的介绍了正则中的元字符。
普通元字符,仅仅匹配这个字符,代表着匹配字符本身的含义
| 字符 | 含义 |
|---|---|
| /a/ | 匹配a这个字符 |
| /string/ | 匹配string这几个字符 |
量词元字符
| 字符 | 含义 |
|---|---|
| ? | 出现一次或者零次 |
| + | 出现一次或者多次 |
| * | 出现零次或者多次 |
| {n} | 出现n次 |
| {n,} | 出现n到多次 |
| {n,m} | 出现n~m次 |
特殊元字符
| 字符 | 含义 | |
|---|---|---|
| \ | 转义字符,可以将那些特殊的元字符转译为普通的字符 | |
| . | 除了换行符(\n)之外的任意字符 | |
| 匹配的字符串以什么开头 | ||
| $ | 匹配的字符串以什么结尾 | |
| \d | 匹配一个0~9的数字字符 | |
| \D | 匹配一个非数字字符 | |
| \w | 匹配一个单字字符(字母、数字或者下划线) | |
| \W | 匹配一个非单字字符 | |
| \b | 匹配一个单词的边界 | |
| \B | 匹配一个非单词边界,/\B../匹配"noonday"中的'oo', 而/y\B../匹配"possibly yesterday"中的’yes‘ | |
| \f | 匹配一个换页符 | |
| \n | 匹配一个换行符 | |
| \r | 匹配一个回车符 | |
| \t | 匹配一个水平制表符 | |
| \v | 匹配一个垂直制表符 | |
| \s | 匹配一个空白字符(包括空格、制表符、换页符和换行符) | |
| \S | 匹配一个非空白字符 | |
| x | y | 匹配x或者y | z |
| [xyz] | 匹配x或者y或者z中的一个 | |
| [^xy] | 匹配除了x和y的任意字符 | |
| [a-z] | 匹配a-z这个范围中的任意字符,[0-9a-zA-Z_] === \W` | ` |
| [^a-z] | 匹配除了a-z这个范围内的其他任意字符 |
以上就是正则中大部分的字符,更多的请查阅MDN官方文档
修饰符
| 字符 | 含义 |
|---|---|
| g | global的缩写,代表全局匹配,即匹配完一个会继续向下匹配,会解决正则的懒惰性(下面再描述) |
| i | ignore的缩写,代表忽略大小写 |
| m | multiline的缩写,代表多行匹配 |
介绍了这么多,接下来进行一些实战环节
实战小demo
- 匹配一个忽略大小写的单词good:
/\bgood\b/i.test('good good study,day day up!'); - 匹配一个只包含数字和小写字母的字符串:
/[0-9]a-z/.test('ac123bd'); - 精确匹配
wonderful regexp:/^wonderful regexp$/.test('wonderful regexp'); ...
[]中的cases
- 在[]中的特殊字符都会呈现出自己原本的作用:
/[@+]/,这样会去匹配@或者+中的一个; - 但是[]中的\d不会呈现自己原本的作用,依旧代表着0-9。
括号分组的概念
在正则表达式中可以通过()来进行分组捕获。
| 字符 | 含义 |
|---|---|
| (x) | 将x进行分组捕获,/\d(\d)/.exec('12')不仅仅捕获大正则,并且将()里面的小正则也捕获,捕获结果为['12', '2'];并且可以将x作为一个引用在后面使用,例如/(x)\1/,就相当于/(x)x/ |
| (?:x) | 只匹配不捕获,/\d(?:\d)/.exec('12')仅仅捕获大正则,小正则不捕获,捕获结果为['12'] |
| x(?=y) | 正向查找,当且仅当x后面紧跟着的是y时才能捕获到,但是y不是捕获的内容获取不到,只是用于检查,/x(?=y)/.test('xy')能捕获,/x(?=y)/.test('xwy')不能捕获 |
| (?<=y)x | 反向查找,当且仅当x前面紧跟着的是y时才能捕获到,但是y不是捕获的内容获取不到,只是用于检查,/(?<=y)x/.test('yx')能捕获,/(?<=y)x/.test('ywx')不能捕获 |
| x(?!y) | 正向否定查找,当且仅当x后面紧跟着的不是y时才能捕获到,但是y不是捕获的内容获取不到,只是用于检查,/x(?!y)/.test('xwy')能捕获,/x(?!y)/.test('xy')不能捕获 |
| (?<!y)x | 反向否定查找,当且仅当x前面紧跟着的不是y时才能捕获到,但是y不是捕获的内容获取不到,只是用于检查,/(?<!y)x/.test('ywx')能捕获,/(?<!y)x/.test('yx')不能捕获 |
实战小demo
- 判断一个英文单词是否符合oo叠词( food,foot,boot等等 ):
/(o)\1/,会成功匹配food; - 查找一个符合前缀的单词( uni-add ):
/(?<=uni-)add/,会成功捕获add; - 查找一个符合后缀的单词( add-het ):
/add(?=het)/,会成功捕获add; - 查找一个符合不前缀的单词( uniu-add ):
/(?<!uni-)add/,会成功捕获add; - 查找一个符合不后缀的单词( add-ihet ):
/add(?!het)/,会成功捕获add。
更多实战可以更具具体情况自己灵活运用哦!
正则中的匹配方法
test
RegExp.prototype.test:判断字符串是否符合正则的规则
exec
RegExp.prototype.exec:将符合正则规则的字符捕获到 捕获身份证上的出生年月:
let regexp = /\d{6}(\d{4})(\d{2})(\d{2})\d{4}/;
let target = '123456202109049901';
let result = regexp.exec(target);
// result -> ['123456202109049901','2021','09','04']
//=>['123456202109049901','2021','09','04', index: 0, input: "123456202109049901"]
//=>第一项:大正则匹配的结果
//=>其余项:每一个小分组单独匹配捕获的结果
//=>如果设置了分组(改变优先级),但是捕获的时候不需要单独捕获,可以基于?:来处理
正则匹配具有懒惰性,即当找到一个符合条件的就立即停止查找,可以通过设置修饰符global取消懒惰性。
// 下面的规则只会匹配一次2-,对于3-不会匹配到,这就是因为正则的懒惰性
/\d-/.exec('2-3-4')
// 下面添加global修饰符就会取消懒惰性,2-和3-都会匹配到
/\d-/g.exec('2-3-4')
exec只会捕获一次,就算设置了global修饰符,需求:设计一个execAll方法实现调用exec获取全部满足规则的结果
//=>需求:编写一个方法execAll,执行一次可以把所有匹配的结果捕获到(前提正则一定要设置全局修饰符g)
~ function () {
function execAll(str = "") {
//=>str:要匹配的字符串
//=>this:RegExp的实例(当前操作的正则)
//=>进来后的第一件事,是验证当前正则是否设置了G,不设置则不能在进行循环捕获了,否则会导致死循环
if (!this.global) return this.exec(str);
//=>ARY存储最后所有捕获的信息 RES存储每一次捕获的内容(数组)
let ary = [],
res = this.exec(str);
while (res) {
//=>把每一次捕获的内容RES[0]存放到数组中
ary.push(res[0]);
//=>只要捕获的内容不为NULL,则继续捕获下去
res = this.exec(str);
}
return ary.length === 0 ? null : ary;
}
RegExp.prototype.execAll = execAll;
}();
正则还具有贪婪性
let str = "正则201@202贪婪性";
//=>正则捕获的贪婪性:默认情况下,正则捕获的时候,是按照当前正则所匹配的最长结果来获取的
let reg = /\d+/g;
console.log(str.match(reg)); //=>["201","202"]
//=>在量词元字符后面设置?:取消捕获时候的贪婪性(按照正则匹配的最短结果来获取)
reg = /\d+?/g;
console.log(str.match(reg)); //=>["2", "0", "1", "2", "0", "2"]
seasrch
String.prototype.search:在字符串中查找满足规则的结果的索引值,不存在输出-1
'abc'.search(/a/) // 0
'abc'.search(/b/) // 1
'abc'.search(/c/) // 2
'abc'.search(/d/) // -1
match
String.prototype.match:去捕获字符串中满足规则的结果,输出为一个数组 match的结果和exec一样,既能捕获大正则也能捕获到小正则中的结果,但是在全局匹配的时候,只能捕获大正则中的结果
//=>不设置g只匹配一次,exec和match获取的结果一致(既有大正则匹配的信息,也有小分组匹配的信息)
let reg = /\{(\d+)\}/;
console.log(reg.exec(str));
console.log(str.match(reg));
//["{0}", "0",...]
*/
let reg = /\{(\d+)\}/g;
//console.log(str.match(reg)); //=>["{0}", "{1}", "{2}"] 多次匹配的情况下,match只能把大正则匹配的内容获取到,小分组匹配的信息无法获取
String.prototype.matchAll
String.prototype.matchAll:全局捕获,将捕获到的结果(包括大正则和小正则)以迭代器的对象形式返回
var regexp = /t(e)(st(\d?))/g;
var str = 'test1test2';
let array = [...str.matchAll(regexp)];
array[0];
// ['test1', 'e', 'st1', '1', index: 0, input: 'test1test2', length: 4]
array[1];
// ['test2', 'e', 'st2', '2', index: 5, input: 'test1test2', length: 4]
// str.matchAll(regexp)可以使用for...of去获取结果
for(result of str.matchAll(regexp)){
console.log(result);
}
// ['test1', 'e', 'st1', '1', index: 0, input: 'test1test2', length: 4]
// ['test2', 'e', 'st2', '2', index: 5, input: 'test1test2', length: 4]
String.prototype.split
String.prototype.split:常用于通过正则分割字符串
// 分割以,-_连接的字符串
'a,b-c_d'.split(/[,\-_]/g); // [a,b,c,d]
String.prototype.replace
String.prototype.replace:常用作实现字符串的替换,将捕获到的结果进行替换
在使用之前先学习replace的语法:
String.prototype.replace( regexp | substr , newSubStr | function )
- regexp:接受一个正则表达式,按照正则的规则去匹配,可以设置全局匹配;
- substr:接受一个字符串,有且仅有匹配一次,将匹配到的字符串片段替换为想要的newSubStr;
- newSubStr:用于替换的字符串,如果是正则匹配,可以使用一些特殊的变量名( $$, $&, $`, $', $n );
- function:可以用于接受一些参数,然后将用于替换的字符串由自己管控。
newSubStr的特殊变量
// $$ 代表插入一个$,将匹配到的结果替换为$
'aa=bb'.replace(/\w=\w/,'$$') // => 'a$b'
// $& 代表匹配到的结果
'aa=bb'.replace(/\w=\w/,'$&') // => 'aa=bb'
'aa=bb'.replace(/\w=\w/,'$&$&') // => 'aa=ba=bb'
// $` 代表匹配到的结果的左侧字符串
'aa=bb'.replace(/\w=\w/,'$`') // => 'aab'
// $' 代表匹配到的结果的右侧字符串
'aa=bb'.replace(/\w=\w/,"$'") // => 'abb'
// $n 代表括号分组小正则匹配的结果,n是正整数,将大正则匹配到的结果替换,
'aa=bb'.replace(/(\w)=(\w)/,"$1") // => 'aab'
'aa=bb'.replace(/(\w)=(\w)/,"$2") // => 'abb'
'aa=bb'.replace(/(\w)=(\w)/,"$2-$1") // => 'ab-ab'
以下有四个不错的快速熟悉正则的挑战:
验证字符出现的次数
需求:找出一个字符串中哪个字符出现的次数最多,出现了几次?
/* let str = "good good study, day day up";
let obj = {};
[].forEach.call(str, char => {
if (typeof obj[char] !== "undefined") {
obj[char]++;
return;
}
obj[char] = 1;
});
let max = 1,
res = [];
for (let key in obj) {
let item = obj[key];
item > max ? max = item : null;
}
for (let key in obj) {
let item = obj[key];
if (item === max) {
res.push(key);
}
}
console.log(`出现次数最多的字符是:${res},出现了${max}次`); */
/* let str = "good good study, day day up";
str = str.split('').sort((a, b) => a.localeCompare(b)).join('');
// console.log(str);
let reg = /([a-zA-Z])\1+/g;
let ary = str.match(reg);
// console.log(ary);
ary.sort((a, b) => b.length - a.length);
// console.log(ary);
let max = ary[0].length,
res = [ary[0].substr(0, 1)];
for (let i = 1; i < ary.length; i++) {
let item = ary[i];
if (item.length < max) {
break;
}
res.push(item.substr(0, 1));
}
console.log(`出现次数最多的字符是:${res},出现了${max}次`); */
/* let str = "good good study, day day up",
max = 0,
res = [],
flag = false;
str = str.split('').sort((a, b) => a.localeCompare(b)).join('');
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}次`);
*/
格式化时间字符串
需求:根据模板规则:{0}->年 {1~5}->月日时分秒将时间字符串进行格式化。
~ function () {
/*
* formatTime:时间字符串的格式化处理
* @params
* templete:[string] 我们最后期望获取日期格式的模板
* 模板规则:{0}->年 {1~5}->月日时分秒
* @return
* [string]格式化后的时间字符串
*/
function formatTime(templete = "{0}年{1}月{2}日 {3}时{4}分{5}秒") {
let timeAry = this.match(/\d+/g);
return templete.replace(/\{(\d+)\}/g, (...[, $1]) => {
let time = timeAry[$1] || "00";
return time.length < 2 ? "0" + time : time;
});
}
/* 扩展到内置类String.prototype上 */
["formatTime"].forEach(item => {
String.prototype[item] = eval(item);
});
}();
let time = "2019-8-13 16:51:3";
console.log(time.formatTime());
console.log(time.formatTime("{0}年{1}月{2}日"));
console.log(time.formatTime("{1}-{2} {3}:{4}"));
time = "2019/8/13";
console.log(time.formatTime());
console.log(time.formatTime("{0}年{1}月{2}日"));
console.log(time.formatTime("{1}-{2} {3}:{4}"));
处理问号传递的参数
需求:获取URL地址问号和面的参数信息。
~ function () {
/*
* queryURLParams:获取URL地址问号和面的参数信息(可能也包含HASH值)
* @params
* @return
* [object]把所有问号参数信息以键值对的方式存储起来并且返回
*/
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[item] = eval(item);
});
}();
let url = "http://www.api.cn/?lx=1&from=wx#video";
console.log(url.queryURLParams());
//=>{lx:1,from:'wx',HASH:'video'}
千分符
需求:实现一个千分符。
~ function () {
/* 扩展到内置类String.prototype上 */
["formatTime", "queryURLParams", "millimeter"].forEach(item => {
String.prototype[item] = eval(item);
});
}();
let num = "15628954"; //=>"15,628,954" 千分符
console.log(num.millimeter());
num = "112345678256874"; //=>"12,345,678,256,874"
console.log(num.millimeter());
完结
结束了,我也学废了!!!