360前端星计划day3
正则表达式的创建和应用
创建的两种方式
1 使用正则表达式字面量
const reg = /[a-z]\d+[a-z]/i;
优点:
- 简单方便,随时用随时写
- 不需要考虑二次转义
缺点
- 子内容无法重复使用
- 过长的正则导致可读性差
2 使用RegExp构造函数
const alphabet = '[a-z]';
const reg = new RegExp('${alphabet}\\d+${alphabet}','i');
优点:
- 子内容可以重复使用,动态创建
- 可以通过控制子内容的粒度提高可读性
缺点
- 二次转义的问题非常容易导致bug
const reg = new RegExp('\d+');
reg.test('1'); //false
reg.test('ddd'); // true
正则表达式的常见用法
验证 RegExp.prototype.test()
const reg = /[a-z]\d+[a-z]/i;
reg.test('ala'); //true
reg.test('1a1'); //false
reg.test(Symbol('ala')); //TypeError
- 输入:字符串,如果不是,会尝试类型转换,转换失败会抛出TypeError
- 输出:true或者false,表示成功或者失败
RegExp.prototype.source和RegExp.prototype.flags
const reg = /[a-z]\d+[a-z]/ig;
reg.source; //"[a-z]\d+[a-z]"
reg.flags; //"gi"
get RegExp.prototype.source
- 返回当前正则表达式的模式文本的字符串
get RegExp.prototype.flags
- 返回当前正则表达式的修饰符的字符串,会对修饰符按照字母升序进行排序
解析:RegExp.prototype.exec()和String.prototype.match()
const reg = /[a-z]\d+[a-z]/i;
reg.exec('a1a');//["a1a", index: 0, input: "a1a", groups: undefined]
reg.exec('1a1'); //null
'a1a'.match(reg);///["a1a", index: 0, input: "a1a", groups: undefined]
'1a1'.match(reg); //null
- 不包含g时候,是一样的
- 包含g时,
exec()
每次返回一条数据,match()
返回所有匹配结果的数组,建议使用exec()
exec()
要求输入字符串,遇到非字符串尝试转换match()
要求输入正则表达式,遇到其他类型先尝试转成字符串,再以字符串为source
创建正则表达式- 成功返回匹配结果(第0项是正则匹配的内容,index是匹配的起始位置,input是匹配内容,group是捕获组),失败返回
null
RegExp.prototype.lastIndex
- 当前正则表达式最后一次匹配成功的位置,第一次是0,比如上一次匹配到了第0个,接下来调用
reg.lastIndex
是1 - 不会自己重置,只有当上一次匹配失败菜会重置为0,反复使用时候需要手动重置
String.prototype.replace()、String.prototype.search()、String.prototype.split()
'a1a'.replace(/a/,'b'); //'b1a'
'a1a'.replace(/a/g,'b'); //'b1b'
'a1a'.search(/a/,'b'); //0
'a1a'.search(/a/g,'b'); //0
'a1a'.split(/a/,'b'); //["",1,""]
场景一:正则与数值
数值判断不简单
/[0-9]+/
[]
:字符集+
:限定符,1或多- 缺点:不是全字符匹配,如`/[0-9]+/.test('a1') === true,只要字符串里包含数字就会返回true
/^\d+$/
^
开始位置,如果结合m是一行的开始$
结束位置,如果结合m是一行的结束- 缺点:不能匹配带符号的数值比如-2,不能匹配小数
/^[+-]?\d+(\.\d+)?$/
()
内是一个子表达式,不带任何修饰符,表示同时创建一个捕获组?
作为限定符时,0或1\.
点匹配除了换行符之外的任意字符,结合s时可以匹配任意字符,所以匹配小数点时候需要转义- 缺点:不能匹配无整数部分的小数,比如.123;捕获组有开销
/^[+-]?(?:\d*\.)?\d+$/
(?:)
创建一个非捕获组*
限定符,0或多- 缺点:不能匹配无小数部分数值,如2. (合法); 不能匹配科学记数法,如1e2
完整的数值正则
/^[+-]?(?:\d+\.?|\d*\.\d+)(?:e[+-]?\d+)?$/i
|
创建分支i
忽略大小写
用正则处理数值
数值的解析
const reg = /[+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(?=px|\s|$)/gi;
function execNumberList(str){
reg.lastIndex = 0;
let exec = reg.exec(str);
const result = [];
while(exec){
result.push(parseFloat(exec[0]));
exec = reg.exec(str);
}
return result;
}
console.log(execNumberList('1.0px .2px -3px +4e1px')); //[1,0.2,-3,40]
(?=expression)
先行断言,用于匹配符合条件的位置(?!expression)
先行否定断言(?<=expression)
后行断言 es2018新增(?<!expression)
后行否定断言 es2018新增g
:修饰符,全局匹配
需要注意的点:
- css中的数值0才可以省略单位,这种情况没有必要靠正则过滤
- 只验证了px单位,还有其他单位和百分比
- 要根据需求追加逻辑
数值转货币
const reg = /(\d)(?=(\d{3})+(,|$))/g
function formatCurrency(str){
return str.replace(reg,'$1,');
}
console.log(formatCurrency('12345678'))
{n}
表示重复n次{n,m}
表示重复n到m次{n,}
表示重复n次以上$n
表示第n个捕获组,n可以是1到9$&
表示本次完整的匹配
场景二:正则与颜色
颜色的表示方式
16进制
color: #rrggbb;
color: #rgb;
color: #rrggbbaa;
color: #rgba;
const hex = '[0-9a-fA-F]';
const reg = new RegExp(`^(?:#${hex}{6}|#${hex}{8}#${hex}{3,4})$`);
rgb/rgba
color: rgb(r,g,b);
color: rgb(r%,g%,b%);
color: rgb(r,g,b,a);
color: rgb(r%,g%,b%,a);
color: rgb(r,g,b,a%);
color: rgb(r%,g%,b%,a%);
const num = '[+-]?(?:\\d*\\.)?\\d+(?:e[+-]?\\d+)?';
const comma = '\\s*,\\s*'; //前后空格
const reg = new RegExp(`rgba?\\(${num}(%?)(?:${comma}${num}\\1){2}(?:${comma}${num}%?)?\\)`); //%?只需要捕获一次,用\1引用
\n
反向引用,表示引用第n个捕获组\s
字符集缩写,用于匹配空白- 注意点:
- r/g/b值是0-255,但是溢出或者小数不会报错
rgb(r,g,b,a)
和rgba(r,g,b)
合法- 当捕获组内容是可选的时候,一定要把问号写在捕获组内,括号不能省略要嵌套
((?:a|b|c)?)
其他表示方法
- hsl
- keywords
使用正则处理颜色
16进制颜色优化
const hex = '[0-9a-z]';
const hexReg = new RegExp(`^#(?<r>${hex})\\k<r>(?<g>${hex})\\k<g>(?<b>${hex})\\k<b>(?<a>${hex}?)\\k<a>$`,'i');
function shortenColor(str){
return str.replace(hexReg,'#$<r>$<g>$<b>$<a>')
}
console.log(shortenColor("#33660000")) //#3600
(?<key>)
具名捕获组(起名字)- 反向引用的语法为
\k<key>
- 在replace中,使用
$<key>
来访问具名捕获组 - 当应用exec时,具名捕获组可以通过
execResult.groups[key]
访问
场景三:正则与URL
用正则解析URL
const protocol = '(?<protocol>https?:)';
const host = '(?<host>(?<hostname>[^/#?:]+)(?::(?<port>\\d+))?)';
const path = '(?<pathname>(?:\\/[^/#?]+)*\\/?)';
const search = '(?<search>(?:\\?[^#]*)?)';
const hash = '(?<hash>(?:#.*)?)';
const reg = new RegExp(`^${protocol}\/\/${host}${path}${search}${hash}$`);
function execURL(url){
const result = reg.exec(url);
if(result){
result.groups.port = result.groups.port || '';
return result.groups;
}
return {
protocol:'',host:'',hostname:'',port:'',
pathname:'',search:'',hash:'',
};
}
console.log(execURL('https://www.360.cn'));
protocol: "https:"
host: "www.360.cn"
hostname: "www.360.cn"
port: ""
pathname: ""
search: ""
hash: ""
console.log(execURL('https://localhost:8080/?a=b#xxxx'));
protocol: "https:"
host: "localhost:8080"
hostname: "localhost"
port: "8080"
pathname: "/"
search: "?a=b"
hash: "#xxxx"
- port捕获组可能是undefined
- 解析失败返回空对象
用正则解析search和hash
function execUrlParams(str){
str = str.replace(/^[#?&]/,'');
const result = {};
if(!str){ //如果正则可能配到空字符串,极有可能造成死循环,判断很重要
return result;
}
const reg = /(?:^|&)([^&=]*)=?([^&]*?)(?=&|$)/y
let exec = reg.exec(str);
while(exec){
result[exec[1]] = exec[2];
exec = reg.exec(str);
}
return result;
}
console.log(execUrlParams('#'));// {}
console.log(execUrlParams('##'));//{'#':''}
console.log(execUrlParams('?q=3606&src=srp')); //{q: "3606", src: "srp"}
console.log(execUrlParams('test=a=b=c&&==&a='));//{test: "a=b=c", "": "=", a: ""}
*?
?可以跟在任何限定符之后,表示非贪婪模式y
粘连修饰符,和g
类似,全局匹配,但是是连续匹配,在match时只会返回第一个匹配结果
总结
- 明确需求
- 考虑全面
- 反复测试