正则表达式基本使用
推荐网址#
正则表达式 regular expression RegExp#
用来处理字符串的规则
- 只能处理字符串
- 它是一个规则: 可以验证字符串是否符合某个规则(test), 也可以,把字符串中符合规则的内容捕获到(exec / match...)
let str = 'good good study , day day up !' ;
// 学正则就是用来制定规则(是否包含数字);
let reg = /\d+/
reg.test(str); // => false
str = '2019-08-12';
reg.exec(str); //=>['2019', index:0, inputs:'原始字符串']
编写正则表达式#
创建方式有两种
// => 字面量创建方式(两个斜杠包起来的,都是用来描述规则的元字符)
let reg1 = /\d+/;
//=>构造函数模式创建 两个参数:元字符字符串, 修饰符字符串
let reg2 = new RegExp("\\d+");
1. 正则表达式由两部分组成#
- 元字符
- 修饰符
/* 常用元字符 */
// => 1.量词元字符: 设置出现的次数
* 零到多次
+ 一到多次
? 零次或者一次
{n} 出现n次
{n,} 出现n到多次
{n, m} 出现n到m次
// => 2.特殊元字符:单个或者组合在一起代表特殊的含义
\ 转义字符(普通->特殊->普通)
. 除\n(换行符)以外的任何字符
^ 以哪一个元字符作为开始
$ 以哪一个元字符作为结束
\n 换行符
\d 0~9之间的一个数字
\D 非0~9之间的一个数字(大写和小写的意思是相反的)
\w 数字、字母、下划线中的任意一个字符
\s 一个空白字符(包括空格、制表符、换页符等)
\t 一个制表符(一个tab键:四个空格)
\b 匹配一个单词的边界
x|y x或者y中的一个字符z
[xyz] x或者y或者z中的一个字符
[^xy] 除了x/y以外的任意字符
[a-z] 指定a-z这个范围中的任意字符 [0-9a-zA-Z_] === \w
[^a-z] 上一个的取反"非"
() 正则中的分组符号
(?:) 只匹配不捕获
(?=) 正向预查
(?!) 负向预查
// => 3.普通元字符: 代表本身含义的
/zengze/ 此正则匹配的就是"zengze"
/* 正则表达式常用的修饰符: img */
i => ignoreCase 忽略单词大小写匹配
m => multiline 可以进行多行匹配
g => global 全局匹配
2. 正则捕获属性#
let reg = /([a-z])([0-9])([A-Z])/;
let result = '@#a1B888'.match(reg);
console.log(result);
/**
* 1.匹配到的字符串部分a1B
* 后面的就是分组 3个分组,值 分别是 a 1 B
* 最后结果 数组中的元素有4个,其他是正则的属性
*/
console.log(result.length); // 4
let arr = ['a1B','a','1','B'];
arr.index = '@#a1B888'.indexOf('a1B');
arr.input = '@#a1B888';
console.log(arr);
//[ 'a1B', 'a', '1', 'B', index: 2, input: '@#a1B888' ]
3. groups属性#
console.log(/(?<x>\d{2})-(?<y>\d{2})/.exec('11-22'));
console.log('11-22'.match(/(?<x>\d{2})-(?<y>\d{2})/));
console.log('11-11'.match(/(?<x>\d{2})-\k<x>/));
console.log('11-22'.replace(/(?<x>\d{2})-(?<y>\d{2})/, "$<y>-$<x>"));
// 给分组起一个名字,你可通过分组名字引用这个分组的值
/*
[
'11-22',
'11',
'22',
index: 0,
input: '11-22',
groups: [Object: null prototype] { x: '11', y: '22' }
]
[
'11-22',
'11',
'22',
index: 0,
input: '11-22',
groups: [Object: null prototype] { x: '11', y: '22' }
]
[
'11-11',
'11',
index: 0,
input: '11-11',
groups: [Object: null prototype] { x: '11' }
]
22-11
*/
元字符详细解析#
1. ^ $
#
let reg = /^\d/
console.log(reg.test('zengze')) // false
console.log(reg.test('2020zengze')) //true
console.log(reg.test('zengze2020')) //false
let reg = /\d$/
console.log(reg.test('zengze')) // false
console.log(reg.test('2020zengze')) //false
console.log(reg.test('zengze2020')) //true
// 两个都不加: 包含字符串规则即可
let reg1 = /\d+/
// 两个都加: 字符串只能是和规则一致的内容
let reg2 = /^\d+$/
//=> 举个例子 : 验证手机号(11位数字, 第一个数字是1即可)
let reg = /^1\d{10}$/
2. \
#
// => 点不是小数点,是除\n以外的任意字符
let reg = /^2.3$/
reg.test('2.3') // true
reg.test('2@3') // true
reg.test('23') // false
// => 基于转义字符, 让其只能代表小数点
reg = /^2\.3$/;
reg.test('2.3') // true
reg.test('2@3') // false
let str = '\\d';
let reg1 = /^\d$/; //\d代表0-9的一个数字
reg.test(str); //false
let reg2 = /^\\d$/ //=>把特殊符号转换为普通的
reg.test(str); //true
3. x|y
#
let reg = /^18|29$/;
reg.test('18') // 全为true
reg.test('29')
reg.test('129')
reg.test('189')
reg.test('1829')
reg.test('829')
reg.test('182')
// -------- 直接用x|y 会存在很乱的优先级问题,一般写的时候都伴随着小括号进行分组,因为小括号改变处理的优先级 => 小括号:分组
let reg = /^(18|29)$/;
reg.test('18') // true
reg.test('29') // true
reg.test('129') // false
reg.test('189') // false
// 只能是x|y中的一个了
4. []
#
// 1.中括号中出现的字符一般都代表本身的含义
let reg = /^[@+]$/;
reg.test('@@'); //false
reg.test('@'); // true
reg.test('+'); // true
reg = /^[\d]$/; // \d在中括号还是0-9
reg.test('d') //false
reg.test('\\') //false
reg.test('9') //true
// 2. 中括号中不存在多位数
reg = /^[18]$/;
reg.test('1') // true
reg.test('8') // true
reg.test('18') // false
reg = /^[10-29]$/ // 1或0-2或9
reg.test('1') // true
reg.test('9') // true
reg.test('0') // true
reg.test('2') // true
reg.test('10') //false
常用的正则表达式#
1. 验证是否式有效数字#
/*
* 规则分析
* 1. 可能出现 + - 号, 也可能不出现 [+-]? 加或减,只能出现0或者1次?
* 2. 一位0-9都可以,多为首位不能是0 (\d|[1-9]\d+)
* 3. 小数部分可能有可能没有,一旦有后面必须有小数点 + 数字(\.\d+)? 也是之恩出现0-1次小数点
*/
let reg = /^[+-]?(\d|[1-9]\d+)(\.\d+)?$/;
/^$/ // 只能是啥啥啥,以什么开头,以什么结尾
2. 验证密码#
// => 数字、字母、下划线
// => 6~16位
let reg = /^\w{6,16}$/
3. 验证真实姓名#
/*
* 1. 汉字 /^[\u4E00-\u9FA5]$/
* 2. 名字长度 2-10位 [\u4E00-\u9FA5]{2,10}
* 3. 可能有译名·汉字 (·[\u4E00-\u9FA5]{2,10}){0,2} 前面小括号里面的内容能出现0-2次
*/
let reg = /^[\u4E00-\u9FA5]{2,10}(·[\u4E00-\u9FA5]{2,10}){0,2}$/
4. 验证邮箱#
let reg = /^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/
//=> \w+((-\w+)|(\.\w+))*
//1. 开头是数字字母下划线(1到多位)
//2. 还可以是 -数字字母下划线 或者 .数字字母下划线,整体零到多次
// => 邮箱的名字由"数字,字母,下划线 -." 几部分组成,大那是-.不能连续出现也不能作为开始
// => @[A-Za-z0-9]+
// 后面紧跟着: 数字,字母 (1-多位)
// => ((\.|-)[A-Za-z0-9]+)* *代表零到多次
// 1. 对@后面的名字的补充
// 多域名 .com.cn
// 企业邮箱
//=>\.[A-Za-z0-9]+
// 这个匹配的是最后的域名(.com/.cn/.org/...)
5. 身份证号码#
/*
* 1. 一共18位
* 2. 最后一位可能是x
*
* 身份证前六位 : 省市县
* 最后八位: 年月日
* 最后四位:
* 最后一位: X或者数字
* 倒数第二位 => 偶数 女 奇数 男
* 其余的是经过算法算出来的
*/
// let reg = /^\d{17}(\d|X)$/
// => 小括号分组的第二个作用:分组捕获,不仅可以把大正则匹配的信息捕获到,还可以单独捕获到每个小分组的内容
let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/
reg.exec("130828199012040617")
//["130828199012040617", "130828", "1990", "12", "04", "1", "7", index: 0, input: "130828199012040617", groups: undefined] 捕获结果是一个数组,包含每一个小分组单独获取的内容
正则两种创建方式的区别#
// => 构造函数因为传递的是字符串,\需要写两个才代表斜杠
let reg = /\d+/g;
reg = new RegExp('\\d+','g');
// => 正则表达式中的部分内容式变量储存的值
// 1. 两个斜杠中间包起来的都是元字符
let type = 'zhufeng';
reg = /^@"+type+"@$/;
console.log(reg.test("@zhufeng@")) //false
//2. 只有构造函数才能用字符串拼接的方式
reg = new RegExp("^@"+type+"@$", 'g');
console.log(reg.test("@zhufeng@")) //true
正则的捕获#
实现正则捕获的方法
正则RegExp.prototype上的方法
exec
test
字符串String.prototype上支持正则表达式处理的方法
- replace
- match
- splite
- .......
let str = "www2019yyy2020zzz2021";
let reg = /\d+/;
/*
* 基于exec实现正则的捕获
* 1. 捕获到的结果是null或者一个数组
* 第一项: 本次捕获的内容
* 其余项: 对应小分组本次单独捕获的内容
* index: 当前捕获内容在字符串中的起始索引
* input: 原始字符串
* 2. 每执行一次exec, 只能捕获到一个符合正则规则的,但是默认情况下,我们执行100遍,获取的结果永远都是第一个匹配到的,其余的捕获不到 => "正则的懒惰性" : 默认只捕获第一个
*/
console.log(reg.exec(str)); //["2019", index: 3, input: "www2019yyy2020zzz2021", groups: undefined]
/*
//=> 实现正则捕获的前提是 : 当前正则要和字符串匹配,如果不匹配捕获的结果是null;
let reg = /^\d+$/;
console.log(reg.test(str)); //false
console.log(reg.exec(str)); // null
*/
正则的懒惰性#
let str = "www2019yyy2020zzz2021";
let reg = /\d+/;
/**
* reg.lastIndex: 当前正则下一次匹配的起始索引位置
* 懒惰性的原因: 默认情况下lastIndex的值不会被修改,每一次都是从字符串开始位置查找,所以只能找到第一个;
* 解决办法 : 全局修饰符"g"
*/
console.log(reg.lastIndex); //=> 0 下面匹配捕获的是从str索引零位置开始找
console.log(reg.exec(str)); //=> 0 第一次匹配捕获完成,lastIndex没有改变,所以下次还是从字符串开始找,找到的永远是第一个匹配到的
let reg = /\d+/g;
console.log(reg.exec(str)); // => ["2019"...]
console.log(reg.lastIndex); //=> 7 设置全局匹配修饰符g后,第一次匹配完,lastIndex会自己修改
console.log(reg.exec(str)); // => ["2020"...]
console.log(reg.lastIndex); //=> 14
console.log(reg.exec(str)); // => ["2021"...]
console.log(reg.lastIndex); //=> 21
console.log(reg.exec(str)); // => null 当捕获到最后一次的时候,再次捕获的时候,结果是null, lastIndex也重新归零,匹配也重新开始了
console.log(reg.lastIndex); //=> 0
console.log(reg.exec(str)); // ["2019"...]
if (reg.test(str)) {
// => 验证一下: 只有正则和字符串匹配我们在捕获
console.log(reg.lastIndex); // => 7 基于test匹配验证后,lastIndex 已经被修改为第一次匹配后的结果,所以下一次捕获不再从头开始了
console.log(reg.exec(str)); // => ["2020"...]
}
// => 需求: 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]);
res = this.exec(str);
}
return ary;
}
RegExp.prototype.execAll = execAll
})()
console.log(reg.execAll(str));
// => 字符串中的match方法,可以在执行一次的情况下,捕获到所有匹配的数据(前提:得设置g才可以)
console.log(str.match(reg));
正则的分组捕获#
let str = "130828199012040112"
let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(?:\d|X)$/
console.log(reg.exec(str))
console.log(str.match(reg))
//=>["130828199012040112", "130828", "1990", "12", "04", "1", "2", index: 0, input: "130828199012040112", groups: undefined]
//=>第一项:大正则匹配的结果
//=>其余项:每一个小分组单独匹配捕获的结果
//=>如果设置了分组(改变优先级),但是捕获的时候不需要单独捕获,可以基于?:来处理
//=>既要捕获到{数字},也想单独的把数字也获取到,例如:第一次找到{0}还需要单独获取0
let str = "{0}年{1}月{2}日"
let reg = /\{(\d+)\}/
//=>不设置g只匹配一次,exec和match结果一样,既有大正则,也有像分组
console.log(reg.exec(str))
console.log(str.match(reg))
// ["{0}", "0", index: 0, input: "{0}年{1}月{2}日", groups: undefined]
//=> 多次匹配的情况下,match只能把大正则匹配的内容获取到,小分组的信息匹配不到
let str = "{0}年{1}月{2}日"
let reg = /\{(\d+)\}/g
console.log(str.match(reg)) //["{0}", "{1}", "{2}"]
//分组捕获
let aryBig = [],
arySmall = [],
res = reg.exec(str);
while(res){
let [big, small] = res;
aryBig.push(big);
arySmall.push(small);
res = reg.exec(str);
}
console.log(aryBig, arySmall);
// ["{0}", "{1}", "{2}"] // ["0", "1", "2"]
//=>分组的第三个作用,"分组引用";
let str = "book"; // good look moon foot
let reg = /^[a-zA-Z]([a-zA-Z])\1[a-zA-Z]$/; //=>分组引用就是通过"\数字"让其代表和对应分组出现一模一样的内容
console.log(reg.test('book')) //true
console.log(reg.test('deep')) //true
console.log(reg.test('some')) //false
正则捕获的贪婪性#
let str = 'aaa2019@2020bbb';
//=>正则捕获的贪婪性:默认情况下,正则捕获的时候,是按照当前正则所匹配的最长的结果来获取的
let reg = /\d+/g
console.log(str.match(reg)); // => ['2019','2020'];
//=>在量词元字符后面设置? 取消捕获时候的贪婪性(按照正则匹配的最短结果来获取)
reg = /\d+?/g;
console.log(str.match(reg)); // ["2", "0", "1", "9", "2", "0", "2", "0"]
问号在正则的五大作用#
- 问号左边是非量词元字符:本身代表量词元字符,出现零到一次;
- 问号左边是量词元字符: 取消捕获时候的贪婪性
- (?:) 只匹配不捕获
- (?=) 正向预查
- (?!) 负向预查
//匹配分组捕获
console.log('1ab'.match(/1([a-z])([a-z])/));
//非捕获分组
console.log('1ab'.match(/1(?:[a-z])([a-z])/));
//正向肯定前瞻 并不消耗掉字符
console.log('1a'.match(/\d(?=[a-z])[a-z]/));
//正向否定前瞻
console.log('1a'.match(/\d(?![A-Z])[a-z]/));
//反向肯定后瞻
//反向否定后瞻
console.log('b1a'.match(/(?<=[a-z])\d[a-z]/));
console.log('A1a'.match(/(?<![a-z])\d[a-z]/));
/*
[ '1ab', 'a', 'b', index: 0, input: '1ab', groups: undefined ]
[ '1ab', 'b', index: 0, input: '1ab', groups: undefined ]
[ '1a', index: 0, input: '1a', groups: undefined ]
[ '1a', index: 0, input: '1a', groups: undefined ]
[ '1a', index: 1, input: 'b1a', groups: undefined ]
[ '1a', index: 1, input: 'A1a', groups: undefined ]
*/
其他正则捕获的方法#
1. test也能捕获(本意是匹配)#
let str = "{0}年{1}月{2}日"
let reg = /\{(\d+)\}/g;
console.log(reg.test(str)); // => true
console.log(RegExp.$1); // => "0"
console.log(reg.test(str)); // => true
console.log(RegExp.$1); // => "1"
console.log(reg.test(str)); // => true
console.log(RegExp.$1); // => "2"
console.log(reg.test(str)); // => false
console.log(RegExp.$1); // => "2" 储存的是上次捕获的结果
// => RegExp.$1~RegExp.$9 获取当次本次正则匹配后,第一个到第九个分组信息
2. replace字符串中实现替换的方法(一般都是伴随正则一起使用的)#
let str = "zzz@2019|zzz@2020";
// => 把 zzz 换成 "中"
// 1. 不用正则一次只能替换一个
str = str.replace('zzz', '中');
console.log(str);
//2.使用正则会简单点
str = str.replace(/zzz/g,'中');
console.log(str);
案例:把时间字符串进行处理
let time = "2019-08-13";
// => 变为 "2019年08月13日"
let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/;
// => 这样可以实现
// time = time.replace(reg, "$1年$2月$3日");
console.log(time); //=> 2019年8月13日
//=>还可以这样处理[str].replace([reg],[function])
//1.首先拿reg和time进行匹配捕获,能匹配到几次就会把传递的函数执行几次(而且是匹配一次就执行一次)
//2.不仅把方法执行,而且replace还给方法传递了实参信息(和exec捕获的内容一致的信息:大正则匹配的内容,小分组匹配的信息...)
//3.在函数中我们返回的是啥,就把当前大正则匹配的内容替换成啥
time = time.replace(reg, (big, $1,$2,$3)=>{
//=> 这里的$1-$3 是我们自己设置的变量
console.log(big, $1,$2,$3) //2019-08-13 2019 08 13
})
time = time.replace(reg, (big, (...arg))=>{
let [,$1, $2, $3] = arg;
$2.length < 2 ? $3 = "0"+$2:null;
$3.length < 2 ? $3 = "0"+$2:null;
return `${$1}年${$2}月${$3}日`;
})
3. 单词首字母大写#
let str = "good good study, day day up!";
let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g;
//=> 函数被执行了六次,每一次都把正则匹配的信息传递给函数
//=> 每一次arg: ["good","g"] //["good","g"]...
str = str.replace(reg, (...arg) => {
let [content, $1] = arg;
$1 = $1.toUpperCase();
content = content.substring(1);
return $1 + content;
})
console.log(str) "Good Good Study, Day Day Up!"
4. 验证一个字符串中,哪个字母出现的次数最多,多少次?#
/* ======去重方法===== */
let str = "zhufengpeixunzhoulaoshi";
let obj = {};
/* 储存每一个出现的次数 */
[].forEach.call(str, char => {
if(typeof obj[char] !== "undefined") { // 如果obj里面有这个属性就++,之后return;
obj[char]++;
return;
}
//没有这个属性,就直接等于1;
obj[char] = 1; // 记录次数;
})
/* 得到最大值 */
let max = 1;
res = [];
for(let key in obj) {
let item = obj[key];
item > max ? max = item : null;
}
/* 得到最大值的 key */
for (let key in obj) {
let item = obj[key];
if(item === max) {
res.push(key);
}
}
console.log(`出现次数最多的字符:${res},出现了${max}次`)
/* ======排序======= */
let str = "zhufengpeixunzhoulaoshi";
str = str.split('').sort((a, b) => a.localeCompare(b)).join(''); // 字符串排序 aeefghhhiilnnoopsuuuxzz
let reg = /([a-zA-Z])\1+/g //分组捕获字母大于1次的内容
let ary = str.match(reg) // ["ee", "hhh", "ii", "nn", "oo", "uuu", "zz"]
ary.sort((a, b) => b.length - a.length) // ["hhh", "uuu", "ee", "ii", "nn", "oo", "zz"]
let res = [ary[0].substr(0, 1)] // 取出数组的第一项的第一个字母
max = ary[0].length
/* 循环数组,如果数组的后一项小于第一项那么就结束循环,否则就加到结果数组中,展示 */
for( let i = 1; i < ary.length ; i++) {
if( max > ary[i].length) {
break;
}
res.push(ary[i].substr(0, 1))
}
console.log(`出现次数最多的字符:${res},出现了${max}次`)
/* ====replace===== */
let str = "zhufengpeixunzhoulaoshi";
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; // 当前循环的i
flag = true; // 找到了就变成true
})
if(flag) break;
}
console.log(`出现次数最多的字符:${res},出现了${max}次`)
5. 常用方法#
~function () {
/**
* @method formatTime : 时间字符串的格式化处理
* @param {string} 要转换的格式 {0}为年 {1-5}为月日小时分钟秒,用户传入
* @return {string} 格式化转换后的字符串
*/
function formatTime(templete = "{0}年{1}月{2}日 {3}时{4}分{5}秒") {
//1.首先获取时间字符串中的年月日等信息
let timeAry = this.match(/\d+/g); // ["2019", "8", "13", "16", "51", "3"]
/* 返回替换后的值 */
return templete.replace(/\{(\d+)\}/g, (...[, $1]) => {
let time = timeAry[$1] || "00"; // time是数组里面匹配到数字的每一项,如果用户传了就是当前数字,每传就为00
return time.length < 2 ? "0" + time : time; // 判断传的数字如果是一位的,就补一个0, 否则直接return 当前值;
});
}
/**
* @method querURLParams: 获取URL地址问号后面的参数信息(可能也包含HASH值)
* @params
* @return {object} 把所有问号参数信息以键值对的方式储存起来并且返回
*/
function querURLParams() {
let obj = {};
this.replace(/([^?=&#]+)=([^?=&#]+)/g, (...[, $1, $2]) => obj[$1] = $2)
this.replace(/(#[^?=&#]+)/g, (...[, $1]) => obj['HASH'] = $1)
return obj
}
/**
* @method millimeter
* @params
* @return {object} 千分符后的字符串
*/
function millimeter() {
/* 正向预查,查到但是不捕获,只捕获前面的部分,然后每次捕获到的加逗号就可以了 */
return this.replace(/\d{1,3}(?=(\d{3})+$)/g, content => content + ',')
}
/* 扩展到内置类String.prototype上 */
["formatTime", "querURLParams", "millimeter"].forEach(item => {
String.prototype[item] = eval(item)
})
}()
/* 时间字符串格式化; */
let time = "2019-8-13 16:51:3";
// => 服务器获取的
// "2019-8-13 16:51:3"
// "2019/8/13 16:51:3"
// => 想要转换的格式
// "08月13日 16时51分"
// "2019年08月13日"
// ...
console.log(time.formatTime()) // 2019年08月13日 16时51分03秒 默认
console.log(time.formatTime("{1}-{2} {3}:{4}")) // 08-13 16:51 // 用户传入格式
/* 得到url的参数 */
let url = "http://www.zhufengpeixun.cn/?lx=1&from=wx#video";
console.log(url.querURLParams()) //{lx: "1", from: "wx", HASH: "#video"}
/* 千分位符 */
let num = "15628954";
console.log(num.millimeter()) // 15,628,954