概述

质上就是一个 JS 函数

用法

双花括号插值中和在 v-bind 表达式中

1
2
3
{{ message | capitalize }}

v-bind:id="rawId | formatId"

定义

组件的选项中定义局部过滤器,优先级高

1
2
3
4
5
6
7
8
9
export default {
filters: {
capitalize: function (value) {
if (!value) return "";
value = value.toString();
return value.charAt(0).toUpperCase() + value.slice(1);
},
},
};

创建 Vue 实例之前使用全局 API,Vue.filter 定义全局过滤器

1
2
3
4
5
Vue.filter("capitalize", function (value) {
if (!value) return "";
value = value.toString();
return value.charAt(0).toUpperCase() + value.slice(1);
});

串联过滤器

{{ message | filterA | filterB }}

  • message 做为参数传给 A,运算结果在穿过 B

{{ message | filterA('arg1', arg2) }}

  • filterA 被定义为接受 3 个参数,message 为第一个参数

原理

src/core/instance/render-helpers.js

  • 模板编译阶段会生成 _f 函数调用字符串,即 resolveFilter 函数,当执行渲染函数的时候,就会执行 _f 函数,从而让过滤器生效
  • 根据过滤器 id 获取到对应的过滤器函数,然后传入参数调用即可
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
31
32
33
34
35
export function resolveFilter(id) {
return resolveAsset(this.$options, "filters", id, true) || identity;
}

export const identity = (_) => _;

/**
* @param {*} options 当前实例的$options属性
* @param {*} type 为filters
* @param {*} id 当前过滤器的id
* @param {*} warnMissing
* @returns 根据过滤器id获取到用户定义的对应的过滤器函数并返回,拿到用户定义的过滤器函数之后,就可以调用该函数并传入参数使其生效了
*/
export function resolveAsset(options, type, id, warnMissing) {
if (typeof id !== "string") {
return;
}
// 两种方式定义的过滤器都会被收集到当前实例的 $options 中,其中使用 Vue.filter 定义的过滤器会被添加到 $options 中的 filters 属性中
const assets = options[type];
// 先从本地注册中查找
// 1. 检查assets自身中是否存在
if (hasOwn(assets, id)) return assets[id];
// 2. 将过滤器id转化成驼峰式后再次查找
const camelizedId = camelize(id);
if (hasOwn(assets, camelizedId)) return assets[camelizedId];
// 3. 将过滤器id转化成首字母大写后再次查找
const PascalCaseId = capitalize(camelizedId);
if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId];
// 4. 再从原型链中查找,还不存在,则在非生产环境下抛出警告
const res = assets[id] || assets[camelizedId] || assets[PascalCaseId];
if (process.env.NODE_ENV !== "production" && warnMissing && !res) {
warn("Failed to resolve " + type.slice(0, -1) + ": " + id, options);
}
return res;
}

串联过滤器

与单个过滤器有所区别的是: 对于多个串联过滤器,在调用过滤器函数传递参数时,后一个过滤器的输入参数是前一个过滤器的输出结果

1
2
3
4
5
6
7
8
9
10
11
{{ message | capitalize }}
// ===> 编译成 _f("capitalize")(message)
// 1. 截取字符串拿到 _f("capitalize") 中定义的 capitalize 函数
// 2. 传入 message 参数执行 capitalize 函数

{{ message | filterA | filterB }}
// ===> 编译成 _f("filterB")(_f("filterA")(message))

{{ message | filterA | filterB(arg) }}
// ===> 编译成 _f("filterB")(_f("filterA")(message), arg)
// 当过滤器接收其余参数时,它的参数都是从第二个参数开始往后传入的

解析过滤器

src/complier/parser/filter-parser.js
解析花括号中的

  • 当作标签文本解析,在 parseHTML 过程中的 chars 钩子函数里调用 parseText,在此调用 parseFilters

解析 v-bind 中的

  • 当作标签属性解析,在 parseHTML 过程中的 processAttrs 里调用 parseFilters
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// > 将过滤器形式转化成 _f("capitalize")(message) 形式
export function parseFilters(exp) {
let inSingle = false; // exp是否在 '' 中
let inDouble = false; // exp是否在 "" 中
let inTemplateString = false; // exp是否在 `` 中
let inRegex = false; // exp是否在 \\ 中
let curly = 0; // 在exp中发现一个 { 则curly加1,发现一个 } 则curly减1,直到culy为0 说明 { ... }闭合
let square = 0; // 在exp中发现一个 [ 则curly加1,发现一个 ] 则curly减1,直到culy为0 说明 [ ... ]闭合
let paren = 0; // 在exp中发现一个 ( 则curly加1,发现一个 ) 则curly减1,直到culy为0 说明 ( ... )闭合
let lastFilterIndex = 0; // 解析游标,每循环过一个字符串游标加1
let c, prev, i, expression, filters;

// 1. 从头开始遍历传入的exp每一个字符,通过判断每一个字符是否是特殊字符(如',",{,},[,],(,),\,|)进而判断出exp字符串中哪些部分是表达式,哪些部分是过滤器id
for (i = 0; i < exp.length; i++) {
prev = c;
c = exp.charCodeAt(i);
if (inSingle) {
if (c === 0x27 && prev !== 0x5c) inSingle = false;
} else if (inDouble) {
if (c === 0x22 && prev !== 0x5c) inDouble = false;
} else if (inTemplateString) {
if (c === 0x60 && prev !== 0x5c) inTemplateString = false;
} else if (inRegex) {
if (c === 0x2f && prev !== 0x5c) inRegex = false;
} else if (
c === 0x7c && // pipe
exp.charCodeAt(i + 1) !== 0x7c &&
exp.charCodeAt(i - 1) !== 0x7c &&
!curly &&
!square &&
!paren
) {
if (expression === undefined) {
// first filter, end of expression
lastFilterIndex = i + 1;
expression = exp.slice(0, i).trim();
} else {
pushFilter();
}
} else {
switch (c) {
case 0x22:
inDouble = true;
break; // "
case 0x27:
inSingle = true;
break; // '
case 0x60:
inTemplateString = true;
break; // `
case 0x28:
paren++;
break; // (
case 0x29:
paren--;
break; // )
case 0x5b:
square++;
break; // [
case 0x5d:
square--;
break; // ]
case 0x7b:
curly++;
break; // {
case 0x7d:
curly--;
break; // }
}
if (c === 0x2f) {
// /
let j = i - 1;
let p;
// find first non-whitespace prev char
for (; j >= 0; j--) {
p = exp.charAt(j);
if (p !== " ") break;
}
if (!p || !validDivisionCharRE.test(p)) {
inRegex = true;
}
}
}
}

// 2. 当匹配到过滤器中的|符时,那么|符前面的字符串就认为是待处理的表达式,将其存储在 expression 中,后面继续匹配,
// 如果再次匹配到过滤器中的 |符 ,并且此时expression有值, 那么说明后面还有第二个过滤器,
// 那么此时两个|符之间的字符串就是第一个过滤器的id,此时调用 pushFilter函数将第一个过滤器添加进filters数组中
// 比如 message | filter1 | filter2(arg) 会被解析成 expression = message;filters = ['filter1','filter2(arg)']
if (expression === undefined) {
expression = exp.slice(0, i).trim();
} else if (lastFilterIndex !== 0) {
pushFilter();
}

function pushFilter() {
(filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim());
lastFilterIndex = i + 1;
}

// 3. 遍历得到的filters数组,并将数组的每一个元素及expression传给wrapFilter函数,用来生成最终的_f函数调用字符串
if (filters) {
for (i = 0; i < filters.length; i++) {
expression = wrapFilter(expression, filters[i]);
}
}

return expression;
}

function wrapFilter(exp, filter) {
// 解析得到的每个过滤器中查找是否有(
const i = filter.indexOf("(");
// 没有说明该过滤器没有接收参数,则直接构造_f函数调用字符串即_f("filter1")(message)并返回赋给expression
if (i < 0) {
// _f: resolveFilter
return `_f("${filter}")(${exp})`;
} else {
// 将新的experssion与filters数组中下一个过滤器再调用wrapFilter函数,
// 如果下一个过滤器有参数,那么先取出过滤器id,再取出其带有的参数,生成第二个过滤器的_f函数调用字符串,即_f("filter2")(_f("filter1")(message),arg)
const name = filter.slice(0, i);
const args = filter.slice(i + 1);
return `_f("${name}")(${exp}${args !== ")" ? "," + args : args}`;
}
}