1.RegExp构造函数
回顾ES5中的RegExp构造函数;
RegExp构造函数语法:
let reg
= new RegExp(text
,flag
);
let reg1
= new RegExp(/xxx/g);
ES5中RegExp构造函数的参数有两种情况
1.传入两个参数:
let reg1
= new RegExp("^[\u4e00-\u9fa5]{0,}$","g");
2.传入一个参数:要注意,ES5语法中,此时不能传入第二个参数用于添加修饰符(会报错);
let reg2
= new RegExp(/^[\u4e00-\u9fa5]{0,}$/g);
let reg3
= /^[\u4e00-\u9fa5]{0,}$/g;
ES6的RegExp构造函数
前面说到,ES5中RedExp构造函数在传入一个正则对象时,不能使用第二个参数添加修饰符,ES6改变了这一行为:如果RegExp构造函数的第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符;返回的正则表达式会忽略原有的修饰符,以新指定的修饰符为准;
// ES6中,如果第一个参数是一个正则对象,此时也可用第二个参数指定修饰符,返回的新字符串以新修饰符为准
let reg4 = new RegExp(/^[\u4e00-\u9fa5]{0,}$/ig,'i');
console.log(reg4.flags); // i 返回的表达式修饰符以后指定的为准
2.字符串的正则方法
字符串对象共有4个方法可以使用正则表达式:
1.match();
2.replace();
3.search();
4.split();
ES6使这4个方法在语言内部全部调用RegExp的实例方法,做到所有与正则相关的方法都定义在RegExp对象上:
String.prototype.match 调用 RegExp.prototype[Symbol.match]
String.prototype.replace 调用 RegExp.prototype[Symbol.replace]
String.prototype.search 调用 RegExp.prototype[Symbol.search]
String.prototype.split 调用 RegExp.prototype[Symbol.split]
// 字符串对象的原型对象方法调用了RegExp对象原型方法对应的方法; Symbol用于表示变量是独一无二的
3.u修饰符
回顾原有的修饰符:
修饰符描述
i匹配时忽略大小写g全局匹配,查找所有匹配的字符m多行匹配,查找满足边界字符^开头和$结尾的字符(每一行的开头和结尾)s默认情况的.是匹配除换行符之外的所有字符,加上s修饰符将包括换行符
ES6为正则表达式添加了u字符,Unicode模式,用来正确处理4个字节的utf-16编码(大于\uFFFF的字符);使用u修饰符能够改变以下修饰符的行为:
1.点字符
默认情况的点字符是匹配除空格外的任意单个字符,点字符处理大于0xFFFF的字符时,需要加上u字符才能识别Unicode字符;
2.Unicode字符表示法
ES6新增了大括号表示Unicode字符的方法,如果不加u修饰符,大括号会被当做量词;
3.量词
使用u修饰符之后,量词能正确识别大于0xFFFF的字符;
4.预定义模式(\S…)
使用u修饰符之后,==预定义模式才能正确识别大于0xFFFF的unicode字符;
5.i修饰符
在某些情况下,仅仅使用i修饰符无法正确识别出非规范的字符,需要i修饰符与u修饰符搭配使用;
4.y修饰符
y修饰符:粘连修饰符;y修饰符的作用与g修饰符类似,都是全局匹配,下一次匹配都从上一次匹配成功的下一个位置开始;y修饰符与g修饰符的不同:g修饰符只要剩余位置中存在匹配即可,但是y修饰符必须要保证匹配是从剩余的第一个位置开始的;
// y修饰符也是全局匹配,但是下一次匹配的结果必须是从上一次匹配成功的下一个位置开始的(所以叫粘连修饰符嘛嘿嘿)
const str = "aaa_aa_a";
let reg1 = /a+/g;
let reg2 = /a+/y;
// 在第一次匹配时,两个修饰符对应的表达式返回的结果是一样滴
let res1 = reg1.exec(str);
console.log(res1); // aaa
let res2 = reg2.exec(str);
console.log(res2); // aaa
// 第二次匹配:g修饰符能正确返回,但是y修饰符由于_的存在,上一次匹配的下一个位置无法匹配到满足条件的字符,所以返回null
let res3 = reg1.exec(str);
console.log(res3); // aa
let res4 = reg2.exec(str);
console.log(res4); // null
5.RegExp.prototype.sticky属性
用于检测表达式是否设置了y修饰符;返回布尔值
// sticky用于检测是否设置了y修饰符
const reg1 = /aaa/;
const reg2 = /aaa/y;
console.log(reg1.sticky); // false
console.log(reg2.sticky); // true
6.RegExp.prototype.flags属性
返回正则表达式的修饰符;
const reg1
= /aaa/;
const reg2
= /aaa/y;
console
.log(reg1
.flags
);
console
.log(reg2
.flags
);
7.s修饰符:dotAll模式
问题引入:.是一个特殊字符,表示任意的单个字符,但是行终止符除外,行终止符包括以下四个:
1.U+000A 换行符(\n);
2.U+000D 回车符(\r);
3.U+2028 行分隔符;
4.U+2029 段分隔符;
s修饰符:使得.可以匹配任意单个字符;dotAll模式:引入s修饰符,使得.可以匹配任意单个字符,该模式被称为dotAll模式,即点(dot)代表一切字符;dotAll属性:返回布尔值,表示正则表达式是否处于dotAll模式下;
// 引入s修饰符,点能表示任意单个字符的模式就是dotAll模式
const reg1 = /peanut.run/;
const reg2 = /peanut.run/s;
const str = "peanut\nrun"
let res1 = reg1.test(str); // false 没有使用s修饰符,无法匹配换行符
let res2 = reg2.test(str); // true 使用s修饰符,点字符代表任意单个字符
// dotAll属性:正则表达式是否处于dotAll模式下
console.log(reg1.dotAll); // false
console.log(reg2.dotAll); // true
8.后行断言
JavaScript只支持先行断言和先行否定断言,不支持后行断言和后行否定断言;
后行断言和后行否定断言是后引入的概念;
先行断言
定义:x只有在y前面才匹配(相邻才行),必须写成/x(?=y)/的形式;
// 先行断言:x只有在y之前才匹配,必须写成/x(?=y)/的形式
const str = "我爱我你爱你";
// 找出爱字前面的我字
let reg = /我(?=爱)/;
let res1 = reg.exec(str); // 我 index=0
先行否定断言
定义:x只有不在y前面才匹配(y后任意位置),必须写成/x(?!y)/的形式;
// 先行断言:x只有不在y前面才匹配,必须写成/x(?=y)/的形式
const str = "我爱我你爱你";
// 先行否定断言 x只有在y后面才匹配 必须写成/x(?!y)/的形式
// 找出我字后面的你字
let reg1 = /你(?!我)/;
let res2 = reg1.exec(str); // 你 index=3
后行断言
定义:与先行断言相反,x只有在y后面才匹配(相邻),必须写成/(?<=y)x/的形式;
// 后行断言:x只有在y后面才匹配 必须写成/(?<=y)x/的形式
const str1 = "好好学习学习";
let reg3 = /(?<=好)学/; // 只有在好字后面的才能匹配
let res3 = reg3.exec(str1); // 学 index=2
后行否定断言
定义:x只有不在y后面才匹配(y前任意位置),必须写成/(?<!y)x/的形式;
// 后行否定断言 x只有不在y后面才匹配(y前任意位置) 必须写成/(?<!y)x/的形式
const str2 = "1213"
let reg4 = /(?<!3)1/; // 匹配3前面的1
let res4 = reg4.exec(str2); // 1 index = 0 说明后行否定断言只要不再y之后,任意位置都可匹配
9.Unicode属性类
ES2018 引入了一种新的类的写法\p{…}和\P{…},允许正则表达式匹配符合 Unicode 某种属性的所有字符;
const regexGreekSymbol = /\p{Script=Greek}/u; // 匹配希腊文
regexGreekSymbol.test('π') // true
Unicode 属性类要指定属性名和属性值;
\p{UnicodePropertyName=UnicodePropertyValue}
10.具名组匹配
简介
正则表达式使用圆括号进行组匹配;
const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/
上面的代码中,正则表达式有三组圆括号,使用exec方法可以将3组匹配结果提取出来;
const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;
const matchObj = RE_DATE.exec("1997-08-06");
console.log(matchObj[1]); // 1997
console.log(matchObj[2]); // 08
console.log(matchObj[3]); // 06
组匹配的一个问题是:每一组匹配的含义不容易看出来,而且只能用数字序号引用,如果组的顺序变了,引用的时候也需要修改对应的序号;具名组匹配的提案允许为每一个组匹配指定一个名字;指定组名的方法:在组匹配的圆括号内部,在模式的头部添加“?”的形式指定组名即可,指定组名之后即可在exec方法返回的结果的groups属性上引用该组名;指定组名后,数字序号仍是可用的;如果具名组没有匹配,对应的groups属性的值会是undefined;
// 具名组匹配,在组匹配时为每个组添加一个名字,便于在exec方法返回结果的groups
属性上根据组名引用对应的组
// “? + < + 组名 + >”的形式指定组名
const reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const matchObj1 = reg.exec("2020-12-21");
// 使用exec方法返回的groups属性通过组名可引用对应的匹配组
let year = matchObj1.groups.year; // 2020
let month = matchObj1.groups.month; // 12
let day = matchObj1.groups.day; // 21
解构赋值和替换
有了具名组匹配以后,可以使用解构赋值直接从匹配结果上为变量赋值;
// 可以通过具名组解构赋值,快捷的为变量赋值
const str = "2020-12-21";
let reg1 = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
let res1 = reg1.exec(str);
let {groups : {year,month,day}} = res1; // 先将groups对象赋值给前面的groups,再将groups中的每个属性依次赋值给对应的变量
console.log(year); // 2020
console.log(month); // 12
console.log(day); // 21
字符串替换时,可以使用$<>引用具名组;
// 字符串替换时,可以使用$<组名>引用具名组
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let res2 = '2015-01-02'.replace(re, '$<day>/$<month>/$<year>');// '02/01/2015'
上面replace方法的第二个参数是字符串,第二个参数也可以是一个函数,该函数的参数序列如下:
'2015-01-02'.replace(re, (
matched, // 整个匹配结果 2015-01-02
capture1, // 第一个组匹配 2015
capture2, // 第二个组匹配 01
capture3, // 第三个组匹配 02
position, // 匹配开始的位置 0
S, // 原字符串 2015-01-02
groups // 具名组构成的一个对象 {year, month, day}
) => {
let {day, month, year} = groups;
return `${day}/${month}/${year}`;
});
具名组匹配在原来的基础上新增了一个函数参数:具名组构成的一个对象。函数内部可以对这个对象进行解构赋值;
引用
要在正则表达式内部引用某个具名组匹配,可以==\k<组名>==的写法;
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false
此时数字引用的方式依然可用:“\数字“;
const RE_TWICE = /^(?<word>[a-z]+)!\1$/;
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false
组名引用和数字引用可以同时使用;
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>!\1$/;
RE_TWICE.test('abc!abc!abc') // true
RE_TWICE.test('abc!abc!ab') // false