简介
大家好,我是六六。在开发中我们经常会使用到过滤器,作用就是来格式化一下我们的文本。但是过滤器原理是什么呢?今天我来为大家揭秘一下,向大家传授一下这过滤器神奇的魔法。当然如果有不对的地方,也请大家多多指教了。
目录
- 过滤器用法
- 过滤器原理概述
- 串联过滤器与参数
- resolveFilter原理
- 过滤器的解析
- 总结
1. 过滤器的用法
Vue.filter( id, [definition] )
参数:
- {string} id
- {Function} [definition]
用法:
注册或获取全局过滤器。
// 注册
Vue.filter('my-filter', function (value) {
// 返回处理后的值
})
// getter,返回已注册的过滤器
var myFilter = Vue.filter('my-filter')
当然了也可以局部注册:
filters:{
myFilter:function(value){
value+='hello world'
return value
}
}
过滤器我们已经注册好了,接下来开始使用了,可以用在两个地方:
<--在双括号中-->
{{message|myFilter}}
<-- 在v-bind中-->
<div v-bind:id='Id|myFilter'></div>
通常情况下,我们也可以串联使用:
{{message|filter1|filter2}}
当然了,也可以传入参数:
{{message|filter1('arg1','arg2')}}
注意注意,实际上我们传入了三个参数,还有一个参数当然使我们要过滤的message,永远是作为第一个参数的。
2.过滤器原理概述
假如模板是这个样子的:
{{message|myFilter}}
这个过滤器在模板编译就会变成这个样子:
_s(_f('myFilter')(message))
看到这个不要慌,其实也是蛮简单的,慢慢来说:
_f函数:
_f这个函数是resolveFilter的别名,作用就是找到我们注册过的'myFilter'这个过滤器,并且返回出去。更通俗的讲,找到我们写的过滤器,并将参数传入进去,执行就ok了。其实就是函数执行嘛。(前提是要找到这个函数哦)
_s函数:
这个函数是toString函数的别名,我们来看看这个函数:
/**
* Convert a value to a string that is actually rendered.
*/
function toString (val) {
return val == null
? ''
: Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
? JSON.stringify(val, null, 2)
: String(val)
}
函数具体细节大家慢慢看,也不是很难的,就是对过滤器执行出来的值在进行一次过滤。
在Vue中,也定了类似这样的很多别名,我们看一下:
function installRenderHelpers (target) {
target._o = markOnce;
target._n = toNumber;
target._s = toString;
target._l = renderList;
target._t = renderSlot;
target._q = looseEqual;
target._i = looseIndexOf;
target._m = renderStatic;
target._f = resolveFilter;
target._k = checkKeyCodes;
target._b = bindObjectProps;
target._v = createTextVNode;
target._e = createEmptyVNode;
target._u = resolveScopedSlots;
target._g = bindObjectListeners;
target._d = bindDynamicKeys;
target._p = prependModifier;
}
有兴趣的大家也可以好好研究,好了回归我们的过滤器,我们来总结一下过滤器的原理: 其实就是执行myFilter过滤器函数并把message当做参数传入,接着我们拿到这个参数在传入toString函数,最终toString函数执行后的结果就会保存到VNode中的text属性中,最后渲染成视图。
3. 串联过滤器与参数
3.1 串联过滤器
{{message|filter1|filter2}}
模板非常简单明了,那么编译后是什么样子呢?
_s(_f('filter2')(_f('filter1')(message)))
从代码中看出,首先执行filter1过滤器,将message传入,返回的结果又当做参数传入filter2,最终返回结果。
3.2 过滤器接受参数
{{message|filter1|filter2('a')}}
编译之后:
_s(_f('filter2')(_f('filter1')(message,'a')))
message是作为第一个参数的,接下来就是函数执行而已,没有什么难度的。
4. resolveFilter原理
之前说过这个函数作用就是帮助我们找到过滤器,那么究竟是怎么找到的呢?这一章慢慢来讲。
在源码中找到这个函数的定义:
/**
* Runtime helper for resolving filters
*/
function resolveFilter (id) {
return resolveAsset(this.$options, 'filters', id, true) || identity
}
这有一行代码,很好理解。通过resolveAsset函数能找到就返回过滤器,找不到就返回identity.
4.1 identity是什么?
/**
* Return the same value.
*/
var identity = function (_) { return _; };
其实identitiy函数很简单,就是传入什么值,就返回什么值。
4.2 resolveAsset大揭秘
先说作用,再看源码,resolveAsset作用就是找到模板中写的过滤器。
/**
* Resolve an asset.
* This function is used because child instances need access
* to assets defined in its ancestor chain.
*/
function resolveAsset (
options,
type,
id,
warnMissing
) {
/* istanbul ignore if */
if (typeof id !== 'string') {
return
}
var assets = options[type];
// check local registration variations first
if (hasOwn(assets, id)) { return assets[id] }
var camelizedId = camelize(id);
if (hasOwn(assets, camelizedId)) { return assets[camelizedId] }
var PascalCaseId = capitalize(camelizedId);
if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId] }
// fallback to prototype chain
var res = assets[id] || assets[camelizedId] || assets[PascalCaseId];
if (warnMissing && !res) {
warn(
'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
options
);
}
return res
}
- 首先判断传入的id是否为字符串,如果不是终止函数执行,默认就返回undefined,也就会执行identitiy函数了。
- 然后声明了变量assets保存了options['filters'],就是过滤器的集合
- 通过hasOwn函数检查assets自身是否含有id属性的值,有的话当然直接返回了。
- 如果没有使用camelize函数将id驼峰化后重新检查自身含有id属性
- 如果还没有使用capitalize将id首字母大写,继续寻找
- 还找到,按照之前顺序重新再找一遍。
5. 过滤器的解析
我们知道过滤器是如何查找的,现在只剩下过滤器是如何编译的了。在Vue中,src/compiler/parser/filter-parser.js中提供了一个parseFilters函数,是专门用来解析过滤器的,来看看如何实现的:
function parseFilters(exp) {
// filters为一个数组,
let filters = exp.split('|')
// 需要格式化的文本表达式
let expression = filters.shift().trim()
let i
if (filters) {
for (i = 0; i < filters.length; i++) {
debugger;
//进入wrapFilter进行拼接
expression = wrapFilter(expression, filters[i].trim())
}
}
return expression
}
function wrapFilter(exp, filter) {
const i = filter.indexOf('(')
// 无参数
if (i < 0) {
return `_f("${filter}")(${exp})`
} else {
const name = filter.slice(0, i)
const args = filter.slice(i + 1)
return `_f("${name}")(${exp},${args}`
}
}
let a = parseFilters(`message|filter1|filter2`)
let b = parseFilters(`message|filter1|filter2('a')`)
console.log(a)
打印结果:
_f("filter2")(_f("filter1")(message))
_f("filter2")(_f("filter1")(message),'a')
解析原理:
parseFilters:首先分别拿到过滤器名字和参数,循环调用wrapFilter函数传入表达式和过滤器名称拼接字符串。
wrapFilter:通过indexOf('(')判断是否有参数,进行不同条件的拼接字符串,最终返回。
大家也可以手动敲一敲,原理也不是很难。但是在Vue中,多了很多很多边界条件判断,将近100行,所以比上面的例子稍微复杂一点。
6. 总结
简单的概述过滤器原理:主要分为两个阶段:
- 编译:在编译阶段把带有过滤器的模板编译成函数调用。
- 执行:拿到编译后的模板,通过_f查询注册的过滤器,进行过滤器函数执行。
本文章参考《Vue.js深入浅出》以及Vue源码