Vue.js 过滤器使用及原理分享讨论
一. 过滤器使用场景/语法/分类
-
使用场景
在做页面的渲染时,过滤器主要帮助我们在不更改原数据的前提下,格式化文本。
例子:
// 页面在做数据渲染时,后台返回的数据格式为枚举类型,如:'01'-通过,'02'-不通过 // 这时可使用过滤器来达到文本格式化的目的 // html <el-table> <el-table-column label='是否通过' width='120'> <template slot-scope='{row}'> {{row.isAgree | filterFormat}} // 注:row.isAgree将作为参数传递给filterFormat </template> </el-table-column> </el-table> // js export default{ data(){ return { agreeTp:{ '01':'通过', '02':'不通过' } } }, filters:{ // 定义过滤器 filterFormat:function(val){ if(!val)return "--";// 如果该属性没值,则在页面显示'--' return this.agreeTp[val] } } }
-
使用语法
2.1 在双花括号中
{{ message | filterFormat }}
2.2 在
v-bind
中<div :v-bind=' message | filterFormat '></div>
-
过滤器的分类
3.1 全局过滤器
Vue.filter('filterFormat', function (value){ if (!value) return '--' return parseInt(value) })
注意:全局过滤器的定义需要放在Vue.js实例创建之前
3.2 局部过滤器
// 在一个组件的选项中定义本地过滤器 filters:{ filterFormat: function (value) { if (!value) return '--' return parseInt(value) } }
二. 过滤器内部运作原理
原理部分的内容主要围绕模板过滤器语法是如何在模板编译阶段被编译成过滤器函数来展开
还记得在第一节中我们在DOM中使用过滤器的两种语法:
{{ message | filterFormat }}
// 或者是
<div :v-bind=' message | filterFormat '></div>
在模板编译阶段过滤器表达式将会被编译为过滤器函数
// 像这个样子的
_s(_f('filterFormat')(message))
-
_f 函数是什么?
1.1 _f 函数全名是:resolveFilter
,这个函数的作用是从this.$options.filters
中找出注册的过滤器并返回_f('filterFormat')(message) // 等价于 this.$options.filters['filterFormat'](message) // message为参数 // 很明显 this.$options.filters 返回一个对象
1.2
resolveFilter
又是如何找到过滤器的呢?让我们先看看
resolveFilter
函数的代码import { indentity,resolveAsset } from 'core/util/index' export function resolveFilter(id){ return resolveAsset(this.$options,'filters',id,true) || identity }
resolveFilter
的实现比较简单,内部直接调用resolveAsset
,将option对象,类型,过滤器id,以及一个触发警告的标志作为参数传递,如果找到,则返回过滤器;找不到,则返回identity
(这里先不讨论identity函数)。所以下一个问号就是
resoveAsset
?1.3
resolveAsset
如何找到过滤器?代码如下:
export function resolveAsset(options,type,id,warnMissing){ // 因为我们找的是过滤器,所以在 resolveFilter函数中调用时 type 的值直接给的 'filters',实际这个函数还可以拿到其他很多东西 if(typeof id !== 'string'){ // 判断传递的过滤器id 是不是字符串,不是则直接返回 return } const assets = options[type] // 将我们注册的所有过滤器保存在变量中 // 接下来的逻辑便是判断id是否在assets中存在,即进行匹配 if(hasOwn(assets,id)) return assets[id] // 如找到,直接返回过滤器 // 没有找到,代码继续执行 const camelizedId = camelize(id) // 万一你是驼峰的呢 if(hasOwn(assets,camelizedId)) return assets[camelizedId] // 没找到,继续执行 const PascalCaseId = capitalize(camelizedId) // 万一你是首字母大写的驼峰呢 if(hasOwn(assets,PascalCaseId)) return assets[PascalCaseId] // 如果还是没找到,则检查原型链(即访问属性) const result = assets[id] || assets[camelizedId] || assets[PascalCaseId] // 如果依然没找到,则在非生产环境的控制台打印警告 if(process.env.NODE_ENV !== 'production' && warnMissing && !result){ warn('Failed to resolve ' + type.slice(0,-1) + ': ' + id, options) } // 无论是否找到,都返回查找结果 return result }
-
_s 函数是什么?
2.1 _s 函数的全称是toString
,过滤器处理后的结果会当作参数传递给toString
函数,最终toString
函数执行后的结果会保存到Vnode
中的text属性中,渲染到视图中。
2.2 _s 函数代码function toString(value){ return value == null ? '' : typeof value === 'object' ? JSON.stringify(value,null,2)// JSON.stringify()第三个参数可用来控制字符串里面的间距 : String(value) }
即先对参数进行判断,没有则直接返回空字符,如果为对象类型则调用
JSON.stringity()
,非对象类型则调用String()
方法在执行过滤器后一定要调用_s函数的原因是动态参数的预期值是一个字符串
让我们回到最初的问题:
模板过滤器表达式是如何被编译成过滤器函数的?
Vue.js中提供了一个parseFilters
函数,专门用来解析过滤器,即在模板编译阶段使用该函数阶段将模板过滤器解析为过滤器函数调用表达式。
parseFilters
的代码实现:
function parseFilters (filter) {
let filters = filter.split('|')
let expression = filters.shift().trim() // shift()删除数组第一个元素并将其返回,该方法会更改原数组
let i
if (filters) {
for(i = 0;i < filters.length;i++){
experssion = warpFilter(expression,filters[i].trim()) // 这里传进去的expression实际上是管道符号前面的字符串,即过滤器的第一个参数
}
}
return expression
}
// warpFilter函数实现
function warpFilter(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}` // 注意这一步少给了一个 ')'
}
}
结语
过滤器的作用是完成数据的格式化
实现原理:在编译阶段将过滤器编译成函数调用(串联过滤器则是一个嵌套的函数调用,前一个过滤器执行的结果是后一个过滤器函数的参数)。编译后通过调用resolveFilter
函数找到对应过滤器并返回,其执行结果作为参数传递给toString
函数,而toString
执行后,其结果会保存在Vnode
的text属性中,渲染到视图。