第十六章:过滤器的奥秘
vue官方文本介绍:
1)使用:
Vue.js允许我们自定义过滤器来格式化文本。它可以用在两个地方:双花括号和v-bind表达式。它应该被添加在JavaScript表达式的尾部,由”管道“符号指示:
1.在双花括号中
{{ message | capitalize}}
2.在v-bind中
<div v-bind:id="rawId | formatId"></div>
2)定义过滤器(在组件中定义和全局定义两种):
1.在组件的选项中定义本地的过滤器:
filter:{ capitalize:function(value){ if(!value) return '' value = value.toString() return value.charAt(0).toUpperCase() + value.slice(1) } }
2.在Vue.js实力之前全局定义过滤器:
Vue.filter('capitalize',function(value){ if(!value) return '' value = value.toString() return value.charAt(0).toUpperCase() + value.slice(1) }) new Vue({ //... ... })
3)补充:
1、过滤器函数总是将表达式的值(之前操作链的结果)作为第一个参数。在上述例子中,capitalize过滤器函数会将收到的message的值作为第一个参数。
2.同时使用多个过滤器,也就是说过滤器可以串联:
{{ message | filterA | filterB }}
在上述例子中,filterA被定义为接受单个参数的过滤器,表达式message的值将作为参数传入到filterA过滤器函数中。然后继续调用同样被定义为接受单个参数的过滤器函数filterB,将过滤器函数filterA的执行结果当作参数传递给filterB函数。
3.过滤器函数可以接收参数,因为过滤器是JavaScript函数。
{{ message | filterA('arg1',arg2) }}
这里,filterA被定义为接收三个参数的过滤器函数。其中message的值作为第一个参数,普通字符串‘arg1’作为第二个参数,表达式arg2的值作为第三个参数。
16.1过滤器原理概述(过滤器是如何执行的)
原理:
例子:{{ message | capitalize }}
上述例子的过滤器在模版编译阶段会编译成下面的样子:
_s(_f("capitalize")(message))
其中_f函数是resolveFilter的别名,其作用是从this.$options.filters中找出注册的过滤器并返回。
因此,上面的例子中的_f("capitalize")与this.$options.filters['capitalize']相同。而this.$options.filters['capitalize']就是我们注册是capitalize过滤器函数:
filter:{ capitalize:function(value){ if(!value) return '' value = value.toString() return value.charAt(0).toUpperCase() + value.slice(1) } }因此,_f("capitalize")(message)其实就是执行了过滤器capitalize并传递了参数message。
_s是toString函数的别名。toString函数代码如下:
function toString(val) { return val = null ? ' ' : typeof val === 'object' ? JSON.Stringify(val,null,2) : String(val) }简单来说,其实就是执行了capitalize过滤器函数并把message当作参数传递进去,接着将capitalize过滤器处理后的结果当作参数传递给toString函数。
最终toString函数执行后的结果会保存到VNode中的text属性中。换句话说,这个返回结果直接被拿去渲染视图了。
16.1.1 串联过滤器
{{ message | capitalize | suffix }}
filters:{
capitalize:function(value){
if (!value) return ' '
value = value.toString()
return value.charAt().toUpperCase() + value.slice(1)
},
suffix:function(value,symbol = '~'){
if(!value) return ' '
return value + symbol
}
}
最终在模板编译阶段会编译成下面的样子:
_s(_f("suffix")(_f("capitalize")(message)))
从代码中可以看出,表达式message的值将作为参数传入到capitalize过滤器函数中,而capitalize过滤器的返回结果通过参数传递给了suffix过滤器,也就是说capitalize过滤器的输出是suffix过滤器的输入。
下图为编译后的串联过滤器,清晰地展示了过滤器的串联过程。
渲染结果:渲染出来的文本的首字母大写并且最后携带~后缀。
16.1.2 滤器接受参数
{{ message | capitalize | suffix('!') }}
设置了参数的过滤器最终被编译后变成这样:
_s(_f("suffix")(_f("capitalize")(message),'!'))
可以看到,加了参数的过滤器和没加参数的过滤器之间唯一的区别就是,当模板被编译之后,会将在模板中给过滤器设置的参数添加在过滤器函数的参数中。
注意:这里是从第二个参数开始,这是因为第一个参数永远都是之前操作链的结果。
下图是接收参数的过滤器与不接收参数的过滤器之间的区别。
16.1.3 resolveFilter的内部原理
通过上文已经大致了解了过滤器是如何运行的,但是还不清楚_f函数是如何找到过滤器的。
_f函数是resolveFilter函数的别名。resolveFilter函数的代码如下:
import { identity, resolveAsset } from 'core/util/index' export function resolveFilter (id){ return resolveAsset(this.$options, 'filters', id , true) || identity }
调用resolveFilter函数查找过滤器,如果找到了,就将过滤器返回;如果找不到,则返回identity。identity函数代码如下:
/** 返回相同的值 **/ export const identity = _=>_
该函数会返回同参数相同的值。
现在看一下resolveAsset函数是如何查找过滤器的,代码如下图:
①首先判断参数id的类型(它是过滤器id),它必须是字符串类型,如果不是,则使用return语句终止函数继续执行。
②然后声明变量assets并将options[type]保存到该变量中。(事实上,resolceAsset函数除了可以查找过滤器外,还可以查找组件和指令。)本例中变量assets中保存的是过滤器集合。
③然后通过hasOwn函数检查assets自身是否存在id属性,如果存在,则直接返回结果。(hasOwn函数基于Object.prototype.hasOwnProperty实现。)④如果不存在,则使用函数camelize将id驼峰化之后再检查assets身上是否存在将id驼峰化之后的属性。⑥如果驼峰化后的属性也不存在,那么使用capitalize函数将id的首字母大写后再次检查assets中是否存在,⑦如果还是找不大,那么按照前面的顺序重新查找一遍属性,不同的是这次将检查原型链。
查找原型链很简单:只需要访问属性即可。如果找到,则返回过滤器。如果找不到,那么在非生产环境下在控制台打印警告。最后,无论是否找到,都返回查找结果。
注册过滤器有两种途径:注册全局过滤器和在组件的选项中定义本地的过滤器。
全局注册的过滤器会保存在Vue构造函数中。
而resolveAsset函数在查找过滤器的过程中并没有去Vue构造函数中搜索过滤器。这是因为在初始化Vue.js实例时,把全局过滤器与组件内注册的过滤器合并到this.$options.filters中了,而this.$options.filters其实同时保存了全局过滤器和组件内注册的过滤器。resolveAsset只需要从this.$options.filters中查找过滤器即可。
16.2 解析过滤器(模版中的过滤器语法是如何编译成过滤器函数来调用表达式的)
例子:
{{ message | capitalize }}
上面的capitalize过滤器是如何被编译成下面的样子的:
_s(_f("capitalize")(message))
在Vue.js内部,src/compiler/parser/filter-parser.js文件中提供了一个parseFilters函数,专门用来解析过滤器的,它可以将模板过滤器解析成过滤器函数调用表达式。逻辑代码如下,我们只需要在解析出过滤器列表后,循环过滤传奇列表并拼接一个字符串即可。
注意:在真是的Vue.js源码中多了很多边界条件判断,所以代码会比上面的例子稍微复杂一点。
上面的split方法将模板字符串切割成过滤器列表,并将列表中的第一个元素赋值给变量expression,然后循环过滤器列表并调用wrapFilter函数拼接字符串。wrapFilter函数接受两个参数(exp,filter),
代码先通过indexOf判断过滤器字符串中是否包含(,如果包含,说明过滤器携带了其他参数,如果不包含,说明过滤器并没有传递其他参数。
不包含字符(时,参数filter就是过滤器ID,所以值需要将它拼接到_f函数的参数并将exp当作过滤器的参数拼接到一起即可。
包含字符(时,需要先从参数filter中将过滤器名和过滤器参数解析出来,而字符( 的左边时过滤器名,右边是参数。例如:filterA('arg1',arg2),字符(左边是过滤器名filterA,右边是参数'arg1',arg2)。由于解析出来的参数右边多了一个小括号,所以在接下来拼接字符串时需要去掉右边的小括号:return '_f("${name}")(${exp},${args}'
16.3 总结
使用Vue.js开发应用时,过滤器是一个常用的功能,用于格式化文本。
用法:
- 基本用法
- 串联
- 接收参数
过滤器的原理:在编译阶段将过滤器编译成函数调用,串联的过滤器编译后是一个嵌套的函数调用,前一个过滤器函数的执行结果是后一个过滤器函数的参数。
编译后的_f函数是resolveFilter函数的别名,resolveFilter函数的作用是找到对应的过滤器并返回。
模板编译过程中过滤器是如何被编译成过滤器函数调用的:1解析,2拼接字符串