🌰

前段时间,在封装一个组件时,考虑到组件的实用性。需要自己实现一个简单的{{ variable }}模板数据绑定技术。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
let listData = [{
name: "张三",
sex: "男",
age: "20"
},{
name: "小明",
sex: "男",
age: "23"
},{
name: "王五",
sex: "女",
age: "21"
}];
let tpl = "<div><span>{{name}}</span><b>{{sex}}</b><s>{{age}}</s></div>"
function translateTpl(data, tpl) {
let newTpl = '';
data.forEach((v) => {
newTpl += tpl.replace(/\{\{(.*?)\}\}/g, (i) => {
return v[i.substring(2,i.length-2)];
});
})
return newTpl;
}
translateTpl(listData, tpl);
// "<div><span>张三</span><b>男</b><s>20</s></div>
// <div><span>小明</span><b>男</b><s>23</s></div>
// <div><span>王五</span><b>女</b><s>21</s></div>"
在实际开发中还需要加上数据类型的判断,错误异常的处理。   

上面的代码就是实现一个简单的数据渲染,将listData中的数据遍历渲染到模板tpl中,产生一个新的用于展示的html代码。其中用就到了正则表达式/\{\{(.*?)\}\}/g,显然这是用来匹配双大括号的,类似于现在很多流行框架的模板数据绑定,不过这是一个简易版。

基础概念

接下来我们就通过上面用到的/\{\{(.*?)\}\}/g来说明一下正则表达式的基础概念。

首先,一个正则表达式的主体部分是有2个斜杠包起来的,至于上例中末尾的g,我们后面会讲到。我们从左到右依次来解释每个字符的含义。

转义字符

没错,我们没看到的第一个字符就是反斜杠。反斜杠在正则表达式中就是叫转义字符。由于有很多字符在正则表达式中有特殊的含义,有时候我们需要将他们当做普通字符来匹配时就需要使用转义符来转义。举个🌰:

1
2
/a+b/.test('a+b'); // false
/a\+b/.test('a+b'); // true

+ 有特殊的含义,所以如果要将它当做普通字符来处理的话就需要用转义符。

一般情况下, \ * + ? | { [ ( ) ] }^ $ . #空白 这些字符都需要转义.

的确有的时候不转义也不会出现问题,比如上述正则表达式中,写成/{{(.*?)}}/g也是能实现想要的效果。不过为了不出现不必要的错误,最好都加上转义符。

所以\{\{就表示连续匹配2个大括号,如果不是特殊字符,直接连在一起就好了。举个🌰:

1
2
/\{\{/.test("{{"); //true
/ab/.test("ab"); //true

括号

接下来的字符就是(

括号 常用来界定重复限定符的范围, 以及将字符分组. 如: (ab)+ 可以匹配abab..等, 其中 ab 便是一个分组.

有时候加括号只是为了方便阅读,本例子便是如此

元字符

接下来的.就是元字符中的一个

元字符 描述
. 匹配除换行符以外的任意字符
\d 匹配数字, 等价于字符组[0-9]
\w 匹配字母, 数字, 下划线
\s 匹配任意的空白符(包括制表符,空格,换行等)
\b 匹配单词开始或结束的位置
^ 匹配行首
$ 匹配行尾

对应也有反义元字符

元字符 描述
\D 匹配非数字的任意字符, 等价于[^0-9]
\W 匹配除字母,数字,下划线之外的任意字符
\S 匹配非空白的任意字符
\B 匹配非单词开始或结束的位置
^x 匹配除x以外的任意字符

重复限定符

*,?是一个重复限定符

a为重复次数
限定符 描述
* a>=0
+ a>=1
a=0 或 a=1
{n} a=n
{n,} a>=n
{n,m} n<=x<=m

那么/\{\{(.*?)\}\}/g中的 (.*?) 是什么意思呢?
按照上面的规则,这里是无法理解的。其实这里涉及到了贪婪模式与非贪婪模式

贪婪模式与非贪婪模式

贪婪模式(默认情况):尽可能多的去匹配字符。
非贪婪模式(在限定词后增加?):尽可能少的去匹配字符。

1
2
3
4
5
let tpl = "<div><span>{{name}}</span><b>{{sex}}</b><s>{{age}}</s></div>"
tpl.match(/\{\{(.*?)\}\}/g);
//["{{name}}", "{{sex}}", "{{age}}"]
tpl.match(/\{\{(.*)\}\}/g);
//["{{name}}</span><b>{{sex}}</b><s>{{age}}"]

一般情况下, 非贪婪模式, 我们使用的是”*?”, 或 “+?” 这种形式, 还有一种是 “{n,m}?”.
区间量词”{n,m}” 也是匹配优先, 虽有匹配次数上限, 但是在到达上限之前, 它依然是尽可能多的匹配, 而”{n,m}?” 则表示在区间范围内, 尽可能少的匹配.

修饰符

例子中的结尾g就是一个修饰符

修饰符 描述
g 全文查找
i 忽略大小写查找
m 多行查找
y ES6新增的粘连修饰符
u ES6新增,用来正确处理大于 \uFFFF 的Unicode字符

g,i比较好理解,下面举例说明下m,y,u

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// m模式
let str="This is an\n an apple";
console.log(/an$/.test(str));
// false
console.log(/an$/m.test(str));
//true
// u模式
/^\uD83D/.test('\uD83D\uDC2A') // true
/^\uD83D/u.test('\uD83D\uDC2A') // false
/吉{2}/.test('吉吉') // false
/吉{2}/u.test('吉吉') // true
// y模式
var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;
console.log(r1.exec(s)) //["aaa", index: 0, input: "aaa_aa_a"]
console.log(r2.exec(s)) //["aaa", index: 0, input: "aaa_aa_a"]
console.log(r1.exec(s)) //["aa", index: 4, input: "aaa_aa_a"]
console.log(r2.exec(s)) //null
console.log(r1.exec(s)) //["a", index: 7, input: "aaa_aa_a"]
console.log(r2.exec(s)) //["aaa", index: 0, input: "aaa_aa_a"]

没有涉及到的好有很多,比较常用的有

字符组

[…] 匹配中括号内字符之一. 如: [xyz] 匹配字符 x, y 或 z. 如果中括号中包含元字符, 则元字符降级为普通字符, 不再具有元字符的功能, 如 [+.?] 匹配 加号, 点号或问号.

排除性字符组

[^…] 匹配任何未列出的字符,. 如: [^x] 匹配除x以外的任意字符.

多选结构

| 就是或的意思, 表示两者中的一个. 如: a|b 匹配a或者b字符.

操作符的运算优先级

  1. \ 转义符
  2. (), (?:), (?=), [] 圆括号或方括号
  3. *, +, ?, {n}, {n,}, {n,m} 限定符
  4. ^, $ 位置
  5. | “或” 操作

常用的正则表达式

  1. 汉字: ^[\u4e00-\u9fa5]{0,}$
  2. Email: ^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*$
  3. URL: ^https?://([\w-]+.)+[\w-]+(/[\w-./?%&=]*)?$
  4. 手机号码: ^1\d{10}$
  5. 身份证号: ^(\d{15}|\d{17}(\d|X))$
  6. 中国邮政编码: [1-9]\d{5}(?!\d) (邮政编码为6位数字)

以上内容仅仅是最常用也是最基础的正则表达式的知识。

参考文献:

正则表达式前端使用手册