1,声明
其声明类似于其他基本类型,可以用字面量或包装声明来声明:
console.log(/abc/g); // /abc/g console.log(new RegExp("abc", "g")); // /abc/g
两者各有优势,字面量声明更符合常规用法,包装类可以更清楚地声明包含变量的正则表达:
const matchStr = "matchStr"; console.log(new RegExp(matchStr, "g")); // /matchStr/g //但并不是说字面量声明不能包含变量,你可以这样做: console.log(eval(`/${matchStr}/g`)); // /matchStr/g //也将分析为正则表达式。eval将字符串参数转换为js表达式,上下文指向当前上下文
2,基本语法
console.log(/ab|cd/.test("ab")); //true console.log(/ab|cd/.test("cd")); //true
// 例如,/是正则的边界符,如果需要在正则中使用符号,则需要这样做: /\//; //在包装类中,转义符需要转义才能正常使用,所以需要这样: console.log(new RegExp("\\/")); // 最终转换为://\// 需要转义的常见字符如下: /\/|\^|\||\$|\(|\)\<|\{|\}|\.|\*|\ |\?|\\/;
//开始边界约束 console.log(/^a/.test("ab")); // true console.log(/^a/.test("ba")); // false console.log(/a/.test("ba")); // true //结束边界约束 console.log(/a$/.test("ba")); // true console.log(/a$/.test("ab")); // false console.log(/a/.test("ab")); // true
//数字与非数字: console.log(/\d/.test("1")); // true console.log(/\D/.test("1")); // false ///大小写字母及下划线,非大小写字母或下划线 console.log(/\w\w\w/.test("a_A")); // true console.log(/\W/.test("a_A")); // false //空白和非空白(空白包括了换行、缩进、空格、制表等和空格相关的) console.log(/\w\s\w\s\w\s\w/.test("b\ta\nc c")); // true console.log(/\S/.test(" ")); // false //全匹配(换行除外): console.log(/./.test("a")); // true console.log(/./.test(" ")); // true console.log(/./.test("_")); // true console.log(/./.test("&")); // true console.log(/./.test("1")); // true
//零或一个 console.log(/^\w?$/.test("")); // true console.log(/^\w?$/.test("a")); // true console.log(/^\w?$/.test("abc")); // false //任意数量(零、一、多) console.log(/^\d*$/.test("")); // true console.log(/^\d*$/.test("1")); // true console.log(/^\d*$/.test("123")); // true //一个或多个 console.log(/^\s $/.test("")); // false console.log(/^\s $/.test(" ")); // false console.log(/^\s $/.test("\n\t")); // false //自定义数量 console.log(/^\d{1,2}$/.test("")); // false console.log(/^\d{1,2}$/.test("1")); // true console.log(/^\d{1,2}$/.test("12")); // true console.log(/^\d{1,2}$/.test("123")); // false //如果省略,就意味着无限 console.log(/a{1,}/.test("aaaaaaaaa")); // true console.log(/a{1}/.test("aaaaaaaaa")); // true //禁止贪婪 ///在上述语法中,每一种匹配都是贪婪的,即尽可能多地匹配符合自己要求的字符 //禁止贪婪可以使其符合条件,尽量少匹配字符 console.log("aaababab".match(/^(\w*)b/)); //['aaababab', 'aaababa', index: 0, input: 'aaababab', groups: undefined] console.log("aaababab".match(/^(\w*?)b/)); //['aaab', 'aaa', index: 0, input: 'aaababab', groups: undefined]
主要配合原子组match方法使用。match在匹配正则时,如果原子组中没有明确声明"?:",捕获原子组中的匹配内容,如:
console.log("abcacb".match(/.(acb)/)[1]; // acb
在正则表达式中,可以使用任何数量的原子组捕获每个原子组的内容:
console.log("abcacbcccddd".match(/abc(acb)(ccc)(ddd)/)); //['abcacbcccddd', 'acb', 'ccc', 'ddd', index: 0, input: 'abcacbcccddd', groups:undefined]
如果在原子组中声明"?:",它的捕获将被忽略:
console.log("abcacbcccddd".match(/abc(acb)(ccc)(?:ddd)/)); //['abcacbcccddd', 'acb', 'ccc', index: 0, input: 'abcacbcccddd', groups: undefined]
这种写法不是一举两得。例如,当你不确定某些整体内容是否会出现时,你可能需要使用原子组将其转化为一个整体,然后使用它" ? "或者" * ",但是你不在乎他的结果,也不需要捕捉,比如:
console.log("abcabcwanted".match(/(?:abc)*(wanted)/)); //['abcabcwanted', 'wanted', index: 0, input: 'abcabcwanted', groups: undefined]
如果使用原子表,只需符合表中任何表达式个体,匹配结果为true,类似或语法:
console.log(/[abcd]*/.test("dacb")); // true
但需要注意的是,原子表将正则分为最小个体,例如abcd,就不再是"abcd"了,而是"a"|"b"|"c"|"d"
console.log(/^[abcd]$/.test("abcd")); // false
//匹配内容之前必须为XXX console.log(/(?<=limit)abc/.test("limitabc")); // true console.log(/(?<=limit)abc/.test("limabc")); // false ///匹配内容后,必须XXX console.log(/abc(?=limit)/.test("abclimit")); // true console.log(/abc(?=limit)/.test("abclim")); // false ///之前不能匹配内容XXX console.log(/(?<!limit)abc/.test("limitabc")); // false console.log(/(?<!limit)abc/.test("limabc")); // true //匹配内容不能是XXX
console.log(/abc(?!limit)/.test("abclimit")); // false
console.log(/abc(?!limit)/.test("abclim")); // true
常用的几个模式修正符如下所示:
i | 表示在和模式进行匹配进不区分大小写 |
m | 将模式视为多行,使用^和$表示任何一行都可以以正则表达式开始或结束 |
s | 如果没有使用这个模式修正符号,元字符中的"."默认不能表示换行符号,将字符串视为单行 |
g | 全局匹配,本意是对字符串进行完整匹配 |
以上只是对正则表达式的语法做了粗浅的介绍,正则表达式在不同组合、不同模式下会产生不同的效果,仅仅纸上谈兵是不够的,都需要自己多手动尝试。
3,基本练习
1,获取字符串中的数字字符,并以数组形式输出。如:12ak3222ljfl444223ql99kmf678,输出:[12, 3222, 444223, 99, 678]。
const reg = /(\d+)/g;
console.log("12ak3222ljfl444223ql99kmf678".match(reg));
//['12', '3222', '444223', '99', '678']
2,将字符串转换为驼峰形式。
const reg = /(-\w)/g;
console.log(
"get-element-by-id".replace(reg, (match) => {
return match.slice(1).toUpperCase();
})
);
//getElementById
3,和谐字符校验,输入的字符不能包含和谐字符 "csdn"。要求:校验必需使用断言
const reg = /^(?!.*csdn.*)[\w\W]*/g; // /[\w\W]*(?<!.*csdn.*)$/g 也同样可以
console.log(reg.test("acsdnbc"));
4,github提交规范校验。要求:feat(1234 section): Added new features 或 fix(222 section): fixed issue
const reg = /^(feat|fix)\((?:(\d+)\s+([^\(\)]+))\):\s*([\w\W]*)$/;
console.log("fix(123 section): make some bug".match(reg));
// ['fix(123 section): make some bug', 'fix', '123', 'section', 'make some bug', index: 0, input: 'fix(123 section): make some bug', groups: undefined]
4,初窥vue源码中的正则表达式
假设你对正则表达式已经有了基本的了解,对其特性也略知一二。现在将会带你以vue中的词法分析、句法分析中出现的正则表达式为例,对其进行逐个分析:
const attribute =
/^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
乍一看会很绕,但是没关系,我们将其拆开来看:
const a = /^\s*/; // 以0~n任意空格数开头
const b = /([^\s"'<>\/=]+)/; // 匹配不为原子表中的这几个字符的字符串
const c = /(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
//第三个比较复杂,再将其拆分来看
const c1 = /\s*(=)\s*/; //匹配"="
let c2 = /(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+))/;
//c2又分为以下三个部分:
const c21 = /"([^"]*)"+/;
// 匹配双引号包裹的内容,匹配方法用到的是双引号中不为双引号的任意字符,且处理了末尾多引号的情况
const c22 = /'([^']*)'+/;
// 匹配单引号包裹的内容,匹配方法用到的是单引号中不为单引号的任意字符,且处理了末尾多引号的情况
const c23 = /([^\s"'=<>`]+)/;
// 匹配除了原子表中的内容,实际上就是不被单双引号包裹的value
c2 = c21 || c22 || c23;
//再将其组装起来,
//c2:匹配单引号,双引号,或不使用引号的内容
//c:匹配 等号 加上 c2内容,且其整体是可以不存在的(用了?))
因此,其最终的匹配结果为如下几种情况:
class="some-class"、class='some-class'、class=some-class、disabled;
const dynamicArgAttribute =
/^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
依旧还是很长,同样的,我们拆分着看:
const dynamicArgAttribute =
/^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
const a = /^\s*/; // 依旧是经典开头
let b = /((?:v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)/;
//其中包括三个部分,继续拆分:
const b1 = /(?:v-[\w-]+:|@|:|#)/;
//匹配v-开头,加上任意数量的字母、下划线、-;或@:# 。即v-[model|text|on|directives] or @ or : or #
const b2 = /\[[^=]+?\]/;
//匹配[]内的不为等号的1~n的字符,对应着v-model[dynamicName],动态属性名
const b3 = /[^\s"'<>\/=]*/;
//匹配不为上述内容的任意数量的字符,对应着修饰符,v-model[dynamicName].modified
b = b1 + b2 + b3;
//因此,b匹配的内容应该为 v-bind[dynamicName].modified
let c = /(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
//继续拆分
const c1 = /\s*(=)\s*/
//匹配等号及其前后的空白
const c2 = /"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)/
//这个在之前已经讲到过了,也就是匹配单引号,双引号,或不使用引号的内容
//并且,因为c末尾处有符号?,其整体可以为0或1
因此,其最终的匹配结果为:
v-bind[dynamicName].sync = "value"
其中,[ ] 及其内容是必须存在的,而指令符可以为v-开头或者几种缩写形式,修饰符可以为0~n,等号及其后面的内容可以为0或1;
上述两个是在词法分析中比较经典的案例,接下来再来看句法分析中的几个正则表达式:
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/;
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/;
从变量名中不难看出,这两个正则是用来处理v-for指令的。但是和词法分析相比,长度上来看,已经易读许多了。但我们依旧将其拆分来看:
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/;
const a = /([\s\S]*?)/
//匹配任意字符,且禁止贪婪(表达式的意思是空白或非空白,二者都可,那么就代表为任意字符全匹配)
//同样的 [\d\D] [\w\W] 也都具有一样的效果。这种写法与 . 相比,可以多匹配换行符
const b = /\s+(?:in|of)\s+/
//匹配前后1~n任意数量的空白,内容为 in 或 of
const c = /([\s\S]*)/
//依旧是一个全匹配
因此,其最终匹配的结果为:(以 v-for = "(item,key,index) in arr" 为例)
(item,key,index),arr。而of和in不参与捕获。
而下一个正则 forIteratorRE 正是处理第一个参数的:
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/;
//匹配 "," 之后,不为原子表中的字符,且匹配一次或两次
最终,其会将第一个参数(item,key,index) 的后两个字段加上其之前的逗号都给捕获:
,key 、,index
至于为什么这么做,可以在代码中略知一二:
const iteratorMatch = alias.match(forIteratorRE);
res.iterator1 = iteratorMatch[1].trim();
if(iteratorMatch[2]) {
res.iterator2 = iteratorMatch[2].trim();
}
res.alias = alias.replace(forIteratorRE, "").trim();
最终,vue会将v-for的第一个参数,解析为:
{
alias:'item'
iterator1:'key'
iterator2:'index'
}
const slotRE = /^v-slot(:|$)|^#/;
这是一个很简单的正则表达式,在此就不做分析,留给大家独立思考。
正则是一个比较常见,且很实用的工具。其语法相对枯燥,但是掌握好正则,绝对会在日常开发中提供大用处。