前言
- 相信大家对正则表达式并不会陌生,它是一种描述了字符串的匹配的模式,可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等
- 虽说不陌生,但我对于正则一直以来的态度都非常暧昧,好像在日常工作中,简单的字符串操作并不需要一定使用正则,而到了一些复杂的场景,那就现查
- 实际这并非一种很好的心态,随着慢慢的深入,一些算法,复杂字符操作,它会变成卡住我的一个点,那么就让我们一起重温正则表达式
- 这个文章可以让我们轻松认识正则~结合 demo~快速理解~
使用场景 - 为啥要用正则
- 在前端工作中,我们一般会有以下场景可以使用正则表达式
- 查找
- 替换
- 验证
- 分割
- 说得挺单调的,上一个例子
- 我们需要把一个字符串里面的数字都找出来,用数组形式展示
const str = 'afdsa234fda231dfsa111';
// [234, 231, 111]
// 不适用正则
function getNumber(str) {
const arr = [];
let tmp = '';
for (let i = 0; i < str.length; i++) {
const s = str[i];
if (!isNaN(s)) {
tmp += s;
} else if (tmp) {
arr.push(tmp);
tmp = ''
}
}
tmp && arr.push(tmp);
return arr
}
// 使用正则
function getNumber(str) {
const reg = /\d+/g;
return str.match(reg);
}
- 从上面的例子我们可以看出,虽然我们可以不适用正则也可以得到相同的结果,但是代码量和工作量并非同一个等量级,这就是使用正则的好处
- 我们可以通过正则表达式,在对字符串进行一些操作时,更加简单、优雅
正则表达式创建
正则表达式的内容
正则表达式分为两个部分
- 需要匹配的内容
- 我们可以通过一些正则的语法进行模糊匹配
- 也可以直接把我们需要匹配的字符写上去进行精确匹配
- 修饰符
- 除了指定匹配的内容外,我们可能需要对匹配内容进行指定额外的匹配策略
- 例如
g
全局匹配i
不区分大小写 等
字面量创建
- 使用
/pattern/flags
进行创建pattern
- 匹配内容flags
- 匹配策略
const str = 'afdsa234aBc231dfsa111';
// const reg = /\d+/g;
const reg = /abc/i;
console.log(str.match(reg));
- 字面量的创建方式最大的优点就是简单,不麻烦
- 除了
\*
的语法之外,他会把里面的内容认定为字符串
构造函数创建
- 在字面量创建时,正则会自动把内容认定为字符串,那么当我们需要使用变量时是操作不了的,这个时候,我们就需要使用构造函数的方式创建
new RegExp(pattern[, flags])
pattern{String}
- 匹配内容flags{String}
- 匹配策略
const str = 'afdsa234aBc231dfsa111';
// const reg = new RegExp('\\d+', 'g');
const abc = '231';
const reg = new RegExp(`${abc}df`, 'g');
console.log(str.match(reg));
- 值得注意的是,我们在使用构造函数创建时,因为是参数是字符串,所以在使用正则语法时,需要加上转义字符
\
- 也是因为的参数是字符串,我们可以对他使用变量拼接
正则匹配方法
正则对象底下方法
test
- 返回一个Boolean
值:是否匹配
const str = 'abc123fda123';
const reg = /\d+/g;
const bol = reg.test(str);
console.log(bol); // true
-
exec
- 返回一个数组,其中存放匹配的结果。如果未找到匹配,则返回值为 null-
返回内容
[0] - 与正则表达式相匹配的文本
[groups] - 捕获匹配到的文本
[index] - 匹配到文本的下标
[input] - 原文本
-
它会忽略全局匹配,基于
lastIndex
开始匹配,如需从新匹配,则手动将lastIndex
设置为 0;
-
const str = 'abc123fda321';
const reg = /(?<test>\d+)/g;
console.log(reg.exec(str)); // [0: "123", 1: "123", groups: {test: '123'}, index: 3, input: "abc123fda321", length: 2]
console.log(reg.exec(str)); // [0: "321", 1: "321", groups: {test: '321'}, index: 9, input: "abc123fda321", length: 2]
// 这里 1 为 RegExp.$1,以此类推
字符串方法
split
- 切割字符串,返回数组- 我们一般传一个字符对原字符进行切割,除此之外,他也可以接受正则
const str = 'abc123fda3214';
console.log(str.split(1)); // ['abc', '23fda32', '4']
console.log(str.split(/\d+/)); // ['abc', 'fda', '']
search
- 查找;类似于indexOf
- 它像是一个可以匹配正则的
indexOf
- 值得注意的是:他不能指定匹配开始的位置,会忽略全局匹配,同时忽略
lastIndexOf
- 它像是一个可以匹配正则的
const str = 'abcdefghiejk';
const reg = /e/g;
console.log(str.search(reg)); // 4
match
- 匹配- 全局匹配 - 得到一个数组,数组内为所有匹配到的字符
- 非全局匹配 - 与
exec
一致
const str = 'abc11defghiej222k';
console.log(str.match(/(?<test>\d+)/)); // [0: "11", 1: "11", groups: {test: '11'}, index: 3, input: "abc11defghiej222k", length: 2]
console.log(str.match(/(?<test>\d+)/g)); // ['11', '222']
replace
- 替换- 返回替换后字符串,并不会修改原本字符串
- 可用函数方式,入参:
arg
- 匹配到的字符groups
- 命名分组的字符index
- 匹配到字符的下标input
- 原字符串
const str = 'abc11defghiej222k';
const reg = /\d/g;
console.log(str.replace(reg, '*')); // abc**defghiej***k
str.replace(reg, (arg, groups, index, input) => {
console.log(arg, groups, index, input);
return '*'
});
字符相关元字符
- 含义:有特殊含义的非字母字符
字符相关
修饰符 | 描述 |
---|---|
\w | 数字、字母、下划线 |
\W | 非 (数字、字母、下划线) |
\d | 数字 |
\D | 非 (数字) |
\s | 空格 |
\S | 非 (空格) |
. | 非 (\n 、\r 、\u2028 、\u2029 ) 注意:模板字符的换行也是换行 |
数量相关
修饰符 | 描述 |
---|---|
{} | 匹配出现次数,3 种写法,详情看实例 |
? | {0,1} 或 让 贪婪匹配 -> 惰性匹配,详情看实例 |
+ | {1,} |
* | {0,} |
{}
const str = 'abceeeffd';
// const reg = /ceeef/g; // 这样写过于麻烦,不可能 100 个就写 100 个 e
let reg = /ce{3}f/g; // 匹配 3 个 e
console.log(reg.test(str));
reg = /ce{1,3}f/g; // 匹配 1-3 个 e,闭区间
console.log(reg.test(str));
reg = /ce{1,}f/g; // 匹配 1-n 个 e
console.log(reg.test(str));
?
const str = '123456789';
let reg = /\d{2,4}/g;
console.log(str.match(reg)); // 贪婪匹配(尽可能多的去匹配) ['1234', '5678']
reg = /\d{2,4}?/g;
console.log(str.match(reg)); // 惰性匹配 ['12', '34', '56', '78']
位置相关
修饰符 | 描述 |
---|---|
^ | 开始 |
$ | 结尾 |
\b | 边界符(\W 的都是边界) |
\B | 非(边界符) |
^
const str = 'abedef';
const reg = /^\w/g;
console.log(str.replace(reg, '*')); // *bedef
$
const str = 'abedef';
const reg = /\w$/g;
console.log(str.replace(reg, '*')); // abede*
\b
const str = 'this is a book';
const reg1 = /is/g;
console.log(str.match(reg1)); // ['is', 'is'] -> 一个是 this 的,一个是 is 的
const reg2 = /\bis\b/g; // 左右空格都是边界
console.log(str.match(reg2)); // ['is']
\B
const str = 'this is a book';
const reg = /\B\w{2}\b/g;
console.log(str.match(reg)); // ['is', 'ok'] -> this['is'] book['ok']
括号相关
修饰符 | 描述 |
---|---|
() | 分组 |
[] | 字符集合 |
| | 或 |
-
()
- 分组
const str = 'abababfdsafds'; const reg1 = /ababab/g; // 太麻烦了写 3 次 console.log(reg1.test(str)); // true const reg2 = /ab{3}/g; // 上面说到的 匹配出现次数 console.log(reg2.test(str)); // false -> 因为他匹配出来相当于 /abbb/g const reg3 = /(ab){3}/g; // 分组的作用 console.log(reg3.test(str)); // true
- 提取
const str = '1996-07-11'; const reg1 = /\d{4}-\d{2}-\d{2}/; console.log(str.match(reg1)); // [0: "1996-07-11", groups: undefined, index: 0, input: "1996-07-11", length: 1] // 通过分组拿到对应值 const reg2 = /(\d{4})-(\d{2})-(\d{2})/; console.log(str.match(reg2)); // [0: "1996-07-11", 1: "1996", 2: "07", 3: "11", groups: undefined, index: 0, input: "1996-07-11", length: 4] console.log(RegExp.$1); // 1996 console.log(RegExp.$2); // 07 console.log(RegExp.$3); // 11
- 替换
const str = '1996-07-11'; // 11/07/1996 const reg = /(\d{4})-(\d{2})-(\d{2})/; console.log(str.replace(reg, '$3/$2/$1')); // 11/07/1996 const res = str.replace(reg, (arg, year, mouth, date) => `${date}/${mouth}/${year}`); console.log(res); // 11/07/1996
- 反向引用
const className = 'news-container_nav'; const reg1 = /\w+(-|_)\w+(-|_)\w+/g; console.log(reg1.test(className)); // true // 一般类名的连接符都是统一的,我们需要做这么一个匹配怎么办? const reg2 = /\w+(-|_)\w+\1\w+/g; // 通过 \1-n 提取之前的分组 console.log(reg2.test(className)); // false
-
[]
- 一般集合
const str = 'My name is LiLei'; // 匹配 LiLei,中间的 L 有可能大写也可能小写 // const reg = /Li(L|l)ei/g; const reg = /Li[Ll]ei/g; // 可以把 L、l 放进集合内,里面本身就有 或者 的意思 console.log(reg.test(str)); // true
- 范围集合
/[0-9]/;/[a-z]/;/[A-Z]/; // 不能写类似于 [a-Z],里面的 [x-x] 都是根据 `ASCII` 码顺序排列的
- 加
^
取反
const str = '123456789'; const reg = /[^0-9]/g; // 中括号内 ^ 意为取反,就是非(0-9) console.log(reg.test(str)); // false
- 取代字符
// \d -> [0-9] // \w -> [a-zA-Z0-9_]
匹配模式
修饰符 | 描述 |
---|---|
g | 全局匹配 |
i | 忽略大小写 |
m | 多行模式 |
s | 让. (修饰符) 全匹配 |
u | 能匹配 unicode 编码 |
y | 粘性模式 |
g
const str = 'abc123daf321fdash';
const reg1 = /\d+/;
console.log(str.match(reg1)); // [0: "123", groups: undefined, index: 3, input: "abc123daf321fdash", length: 1]
const reg2 = /\d+/g;
console.log(str.match(reg2)); // ['123', '321']
i
const str = 'abcABc';
const reg1 = /ABC/g;
console.log(reg1.test(str)); // false
const reg2 = /ABC/gi;
console.log(reg2.test(str)); // true
m
const str = `abc
efg
hij`;
const reg1 = /^\w/g;
console.log(str.replace(reg1, '*'));
/*
*bc
efg
hij
*/
const reg2 = /^\w/gm;
console.log(str.replace(reg2, '*'));
/*
*bc
*fg
*ij
*/
s
const str = `abc
efg`;
const reg1 = /^a.*g$/g;
console.log(reg1.test(str)); // false
const reg2 = /^a.*g$/gs;
console.log(reg2.test(str)); // true
u
const str = 'a';
const reg = /\u{61}/gu;
console.log(reg.test(str)); // true
y
const str = '12345fdafdsa4324';
const reg1 = /\d/g;
const reg2 = /\d/gy;
console.log(reg1.exec(str)[0], reg2.exec(str)[0]); // 1
console.log(reg1.exec(str)[0], reg2.exec(str)[0]); // 2
console.log(reg1.exec(str)[0], reg2.exec(str)[0]); // 3
console.log(reg1.exec(str)[0], reg2.exec(str)[0]); // 4
console.log(reg1.exec(str)[0], reg2.exec(str)[0]); // 5
console.log(reg1.exec(str)); // [0: 4]
console.log(reg2.exec(str)); // null -> 匹配必须连续的
命名分组和零宽断言
命名分组
- 在上面
exec
和match
方法的时候,我们总会看到一个groups
,其实他就是一个我们自定义命名的一个对象 - 我们可以通过
?<xxx>
进行命名分组
const str = '1996-07-11';
const reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
console.log(str.match(reg)); // [0: "1996-07-11", 1: "1996", 2: "07", 3: "11", groups: { year: '1996', month: '07', day: '11' }, index: 0, input: "1996-07-11", length: 4]
零宽断言
-
当某个值为条件,而我们又不想让他被替换,这时可以使用零宽断言
-
零宽断言分
正向
和负向
,都分别有肯定
和否定
- 正向:条件在后
- 负向:条件在前
- 肯定:符合要求替换
- 否定:不符合要求替换
-
结合 demo 理解嗷~
正向零宽断言
const str = 'iphone4iphone5iphone10iphoneNumber'; // '苹果4苹果5苹果10iphoneNumber'
const reg1 = /iphone\d+/g;
console.log(str.replace(reg1, '苹果')); // 苹果苹果苹果iphoneNumber
// \d+ 是条件,但我又不想让他被替换
// 肯定
const reg2 = /iphone(?=\d+)/g;
console.log(str.replace(reg2, '苹果')); // 苹果4苹果5苹果10iphoneNumber
// 否定
const reg3 = /iphone(?!\d+)/g;
console.log(str.replace(reg3, '苹果')); // iphone4iphone5iphone10苹果Number
负向零宽断言
const str = '10px20px30pxipx'; // 10像素20像素30像素ipx
const reg1 = /\d+px/g;
console.log(str.replace(reg1, '像素')); // 正向零宽断言
// \d+ 是条件,但我又不想让他被替换
// 肯定
const reg2 = /(?<=\d+)px/g;
console.log(str.replace(reg2, '像素')); // 10像素20像素30像素ipx
// 否定
const reg3 = /(?<!\d+)px/g;
console.log(str.replace(reg3, '像素')); // 10px20px30pxi像素