360星计划-正则的三个应用场景

一、正则表达式的创建与使用

1、创建正则表达式的两种方法

a)使用正则表达式字面量
const reg = /[a-z]\d+[a-z]/i;

[a-z]: 表示从a到z的字符集
\d:表示从数字0到数字9的数字集
 +:为限定符,限定前一个字符集(\d)重复一次到多次
 i:表示当前正则表达式忽略大小写

优点:

  • 简单方便
  • 不需要考虑二次转义

缺点:

  • 子内容无法重复使用
  • 过长的正则导致可读性差
b)使用RegExp构造函数
const alphabet = '[a-z]';
const reg = new RegExp(`${alphabet}\\d+${alphabet}`, 'i');

RegExp参数(都是string):
1、正则表达式的内容
2、修饰符

\\d:对\d进行转义

优点:

  • 子内容可以重复使用
  • 可以通过控制子内容的粒度提高可读性

缺点:

  • 二次转义的问题非常容易导致 bug
const reg = new RegExp(`\d+`);
reg.test('1'); // false
reg.test('ddd'); // true

出现问题原因:忘记对\d中的\进行转义

2、正则表达式的常见用法

a)RegExp.prototype.test()
const reg = /[a-z]\d+[a-z]/i;

reg.test('a1a'); // true
reg.test('1a1'); // false
reg.test(Symbol('a1a')); // TypeError

输入:

  • 要求输入字符串,如果输入的不是字符串类型,会尝试进行类型转换,转换失败会抛出 TypeError

输出:

  • true 或者 false,表示匹配成功或失败
b)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:

  • es2015新增,返回当前正则表达式的修饰符的字符串,会对修饰符按照字母升序进行排序(gimsuy)
c)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

返回的数组:
0:正则表达式匹配到的完整内容(有捕获组会从第一项开始往下排)
1:index表示内容在完整字符串里的起始位置
2:input为输入的完整字符串
3:group为具名捕获组

输入:

  • RegExp.prototype.exec 要求输入字符串,遇到非字符串类型会尝试转换
  • String.prototype.match 要求输入正则表达式,遇到其它类型会先尝试转成字符串,再以字符串为 source 创建正则表达式

输出:

  • 匹配成功,返回匹配结果
  • 匹配失败,返回 null
const reg = /(a)/g;

reg.exec('a1a'); // ["a", "a", index: 0, input: "a1a", groups: undefined]
'a1a'.match(reg); // ["a", "a"]
  • 当正则表达式含有 g 修饰符时,RegExp.prototype.exec 每次只返回一个匹配结果,数据格式和不含 g 修饰符相同。
  • String.prototype.match 会返回所有的匹配结果,数据格式会变为字符串数组。
  • 由于 String.prototype.match 返回的数据格式不固定,因此大多数情况都建议使用 RegExp.prototype.exec
d)RegExp.prototype.lastIndex
const reg = /(a)/g;
const str = 'a1a';

reg.lastIndex; // 0,默认指向0
reg.exec('a1a'); // ["a", "a", index: 0, input: "a1a", groups: undefined]
reg.lastIndex; // 1
reg.exec('a1a'); // ["a", "a", index: 2, input: "a1a", groups: undefined]
reg.lastIndex; // 3
reg.exec('a1a'); // null
reg.lastIndex; // 0

当前正则表达式最后一次匹配成功的结束位置(也就是下一次匹配的开始位置)

注意:lastIndex 不会自己重置,只有当上一次匹配失败才会重置为 0 ,因此,当你需要反复使用同一个正则表达式的时候,请在每次匹配新的字符串之前重置 lastIndex!

e)String.prototype.replace()、String.prototype.search()、String.prototype.split()
'a1a'.replace(/a/, 'b'); // 'b1a'
'a1a'.replace(/a/g, 'b'); // 'b1b'

'a1a'.search(/a/); // 0
'a1a'.search(/a/g); // 0

'a1a'.split(/a/); // ["", "1", ""]
'a1a'.split(/a/g); // ["", "1", ""]

二、场景一:正则与数值

1、数值判断不简单

a)/[0-9]+/

[ ]:

  • 字符集,使用连字符 - 表示指定的字符范围,如果想要匹配连字符,需要挨着方括号放置,或进行转义
  • 0-9 表示匹配从 0 到 9 的数字字符,常用的还有 a-z 匹配小写字母,\u4e00-\u9fa5 匹配汉字等
  • 如果只是匹配数字,还可以使用字符集缩写 \d

+:

  • 限定符,匹配一个或多个

缺点:

  • 不是全字符匹配,存在误判,如 /[0-9]+/.test(‘a1’) === true
b)/^\d+$/

^:

  • 匹配字符串开始位置,当结合 m 修饰符时,匹配某一行开始位置

$:

  • 匹配字符串结束位置,当结合 m 修饰符时,匹配某一行结束位置

缺点:

  • 不能匹配带符号的数值,如 +1,-2
  • 不能匹配小数,如 3.14159
c)/^ [+ -]?\d+(.\d+)?$/

():

  • 圆括号内是一个子表达式,当圆括号不带任何修饰符时,表示同时创建一个捕获组

?:

  • ? 在正则中有多种含义,作为限定符时,表示匹配零到一个

\ .:

  • . 可以匹配除换行符之外的任意字符,当结合 s 修饰符时,可以匹配包括换行符在内的任意字符
  • 当匹配小数点字符时需要转义

缺点:

  • 不能匹配无整数部分的小数,如 .123
  • 捕获组会带来额外的开销
d)/^ [+ -]?(?:\d*.)?\d+$/

(?:):

  • 创建一个非捕获组

*:

  • 限定符,匹配0个或多个

缺点:

  • 不能匹配无小数部分的数值,如 2.
  • 不能匹配科学计数法,如 1e2、3e-1、-2.e+4

2、完整的数值正则怎么写?

a)完整的数值token

number-token
注意:这个 token 是 CSS 的 token,在 javascript 中,要多考虑一种情况

+'2.'; // 2
+'2.e1'; // 20
b)/^ [+ -]?(?:\d+.?|\d*.\d+)(?:e[+ -]?\d+)?$/i

|:

  • 用来创建分支,当位于圆括号内时,表示子表达式的分支条件,当位于圆括号外时,表示整个正则表达式的分支条件

i修饰符:

  • 表示匹配时忽略大小写,在这个例子中用于匹配科学计数法的 e,去掉 i 修饰符需要把 e 改为 [eE]

注意:16进制等的数值情况还是不在其中

3、用正则处理数值

a)数值的解析
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]
console.log(execNumberList('+1.0px -0.2px 3e-1px')); // [1, -0.2, 0.3]
console.log(execNumberList('1px 0')); // [1, 0]
console.log(execNumberList('-1e+1px')); // [-10]=(正向肯定环视 / 顺序肯定环视 / 先行断言):用于匹配符合条件的位置

类似的语法还有:
(?!expression) 正向否定环视 / 顺序否定环视 / 先行否定断言
(?<=expression) 反向肯定环视 / 逆序肯定环视 / 后行断言,es2018 新增
(?<!expression) 反向否定环视 / 逆序否定环视 / 后行否定断言,es2018 新增
const reg = /[+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(?=px|\s|$)/gi;

g:修饰符,表示全局匹配,用于取出目标字符串中所有符合条件的结果

注意;

  • 按照 CSS 规范,只有数值为 0 才可以省略单位,这种情况没有必要靠正则来过滤
  • 这个例子中只验证了 px 单位,实际还存在 pt、em、vw 等单位,并且没有考虑百分比的情况
  • 实际工作中,要根据需求追加处理逻辑
b)数值转货币符号
const reg = /(\d)(?=(\d{3})+(,|$))/g;
function formatCurrency(str) {
   return str.replace(reg, '$1,');
}

console.log(formatCurrency('1')); // 1
console.log(formatCurrency('123')); // 123
console.log(formatCurrency('12345678')); // 12,345,678

{n}:限定符,表示重复n次,n必须为非负整数
{n, m} 表示重复 n 到 m 次,n 和 m 都必须是非负整数,且 n <= m
{n,} 表示重复 n 次以上
$n:用于 replace 的字符串中,表示第 n 个捕获组,n 可以从 19
$&:表示本次完整的匹配,上面代码可修改为:
const reg = /\d(?=(?:\d{3})+(?:,|$))/g;
function formatCurrency(str) {
   return str.replace(reg, '$&,');
}

注意:环视中的圆括号也会生成捕获组,所以都要采用 (?: ) 的非捕获组形式

三、场景二:正则与颜色

1、颜色的表达方式

a)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})$`);

注意:也可以使用 i 修饰符来匹配大小写,i 修饰符和 a-fA-F 要根据实际需求来做取舍

b)rgb/rgba表示法
color: rgb(r, g, b);
color: rgb(r%, g%, b%);
color: rgba(r, g, b, a);
color: rgba(r%, g%, b%, a);
color: rgba(r, g, b, a%);
color: rgba(r%, g%, b%, a%);

对应的正则写法:

const num = '[+-]?(?:\\d*\\.)?\\d+(?:e[+-]?\\d+)?';
const comma = '\\s*,\\s*';
const reg = new RegExp(`rgba?\\(\\s*${num}(%?)(?:${comma}${num}\\1){2}(?:${comma}${num}%?)?\\s*\\)`);

\n:反向引用,表示引用第 n 个捕获组,由于 r/g/b 必须同时为数值或百分比,所以 %? 只需要捕获一次,用 \1 来引用

\s:字符集缩写,用于匹配空白

注意

  • 按照规范,rgb(r,g,b,a) 和 rgba(r,g,b) 也是合法的
  • r/g/b 的值应该是 0~255 的整数,但是溢出或小数并不会报错
  • 当捕获组内的内容是可选的时候,一定要把问号写在捕获组内
  • 如果可选内容的圆括号不可省略,如(a|b|c)?,应该多嵌套一层:((?:a|b|c)?)
c)其他
/* hsl & hsla */
color: hsl(h, s%, l%);
color: hsla(h, s%, l%, a);
color: hsla(h, s%, l%, a%);

/* keywords */
color: red;
color: blue;
/* …… */

更多颜色表示方法

2、使用正则处理颜色

a)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('#336600')); // '#360'
console.log(shortenColor('#19b955')); // '#19b955'
console.log(shortenColor('#33660000')); // '#3600'

(?<key>)- es2018 新增,具名捕获组
- 反向引用时的语法为 \k<key>
- 在 replace 中,使用 $<key> 来访问具名捕获组
- 当应用 exec 时,具名捕获组可以通过 execResult.groups[key] 访问

四、场景三:正则与url

1、正则解析url

a)完整的url规范

参考
​​
url解析
简单起见,scheme 只匹配 http 和 https ,忽略 userinfo 部分

b)解析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'));
console.log(execURL('http://localhost:8080/?#'));
console.log(execURL('https://image.so.com/view?q=360&src=srp#id=9e17bd&sn=0'));
console.log(execURL('this is not a url'));

注意

  • port 捕获组可能为 undefined
  • 要考虑解析失败的情形

2、正则解析search和hash

a)完整解析
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=360&src=srp')); // { q: '360', src: 'srp' }
console.log(execUrlParams('test=a=b=c&&==&a=')); // { test: 'a=b=c', '': '=', a: '' }

*?:
? 可以跟在任何限定符之后,表示非贪婪模式(注意:这个例子其实不太恰当,使用贪婪模式效果是一样的)

y:es6 新增,粘连修饰符,和 g 修饰符类似,也是全局匹配。区别在于:y 修饰符每次匹配的结果必须是连续的;y 修饰符在 match 时只会返回第一个匹配结果

注意
正则表达式如果可能匹配到空字符串,极有可能造成死循环,所以这段代码很重要:

if (!str) {
        return result;
    }
b)解析制定key
function getUrlParam(str, key) {
...//还在努力中。。。
}

console.log(getUrlParam('?nothing', 'test')); // ''
console.log(getUrlParam('#a=1&aa=2&aaa=3', 'a')); // '1'
console.log(getUrlParam('&b=1&a=1&b=2', 'b')); // '2'
console.log(getUrlParam('a=1&b=2&c=&d', 'c')); // ''
console.log(getUrlParam('&d==', 'd')); // '='
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值