js中正则的三个应用场景

676

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时只会返回第一个匹配结果

总结

  • 明确需求
  • 考虑全面
  • 反复测试