我们先来简单回顾下过滤器的简单用法
<!-- 在双花括号中 -->
{{ message | capitalize }}
<!-- 在v-bind中 -->
<div v-bind:id="rawId | formatId"></div>
在组件中定义过滤器
// 定义过滤器
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
或者定义全局过滤器
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
new Vue({
// ……
})
下面是一个串联过滤器的例子:
message的值将作为参数传入到filterA,filterA的执行结果会传入到filterB中
{{ message | filterA | filterB }}
过滤器可以接收参数
// filterA为接收三个参数的过滤器函数,message是一个参数
{{ message | filterA('arg1', arg2) }}
# 过滤器的原理
{{ message | capitalize }}
将编译成
_s(_f("capitalize")(message))
_f是resolveFilter的别名,_f("capitalize")和this.$options.filters['capitalize']相同,作用是从this.$options.filters中找出注册的过滤器并返回。this.$options.filters同时保存了全局过滤器和组件内注册的过滤器,在初始化Vue.js实例时,会把全局过滤器与组件内注册的过滤器合并到this.$options.filters中了。
_f("capitalize")(message)就是把message当做参数传给过滤器并执行过滤器。
_s函数是toString函数的别名
function toString (val) {
return val == null
? ''
: typeof val === 'object'
? JSON.stringify(val, null, 2)
: String(val)
}
概括下上面的步骤就是:
- 执行capitalize函数,并把message当做参数传入
- 将处理结果当做参数传给toString函数
- 将toString的结果保存到Vnode中的text属性中,用这个结果去渲染视图
下面说下串联过滤器
{{ message | capitalize | suffix }}
// 本地定义的过滤器
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
},
suffix: function (value, symbol = '~') {
if (!value) return ''
return value + symbol
}
}
上述模板编译成
// message的值作为参数传入到capitalize过滤器函数中,capitalize过滤器的返回结果通过参数传递给了suffix过滤器
_s(_f("suffix")(_f("capitalize")(message)))
渲染出来的结果是文本的首字母大写并且最后携带~后缀。
如果在过滤器中传入参数会怎么处理呢?
{{message|capitalize|suffix('!')}}
编译结果为
_s(_f("suffix")(_f("capitalize")(message),'!'))
加了参数的过滤器会将在模板中给过滤器设置的参数添加在过滤器函数的参数中。
下面我们来说下_f函数也就是resolveFilter
import { identity, resolveAsset } from 'core/util/index'
export function resolveFilter (id) {
// 调用该函数查找过滤器,如果找到了,则将过滤器返回;如果找不到,则返回identity
return resolveAsset(this.$options, 'filters', id, true) || identity
}
/**
* 返回相同的值
*/
export const identity = _ => _
export function resolveAsset (options, type, id, warnMissing) {
// 如果过滤器id不是字符串则直接返回
if (typeof id !== 'string') {
return
}
// assets用来保存的过滤器集合
const assets = options[type]
// hasOwn函数检查assets自身是否存在id属性,如果存在,则直接返回结果
if (hasOwn(assets, id)) return assets[id]
// 使用函数camelize将id驼峰化之后再检查assets身上是否存在将id驼峰化之后的属性
const camelizedId = camelize(id)
if (hasOwn(assets, camelizedId)) return assets[camelizedId]
// 使用capitalize函数将id的首字母大写后再次检查assets中是否存在
const PascalCaseId = capitalize(camelizedId)
if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
// 检查原型链
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
}
但是模板中的过滤器语法是怎么编译成过滤器函数调用表达式的呢?
{{ message | capitalize }}
// 是怎么编译成下面这样的呢
_s(_f("capitalize")(message))
parseFilters函数,专门用来解析过滤器,它可以将模板过滤器解析成过滤器函数调用表达式
export function parseFilters (exp) {
// 使用split方法将模板字符串切割成过滤器列表
let filters = exp.split('|')
// 将列表中的第一个元素赋值给变量expression
let expression = filters.shift().trim()
let i
// 循环过滤器列表
if (filters) {
for (i = 0; i < filters.length; i++) {
// 调用wrapFilter函数拼接字符串
expression = wrapFilter(expression, filters[i].trim())
}
}
return expression
}
/*
exp :表达式 String
filter: 过滤器 String
*/
function wrapFilter (exp, filter) {
// 判断过滤器字符串中是否包含字符(
const i = filter.indexOf('(')
// 如果包含,说明过滤器携带了其他参数
if (i < 0) {
// 不包含字符(的情况,参数filter就是过滤器ID,所以只需要将它拼接到 _f函数的参数并将exp当作过滤器的参数拼接到一起即可。
return `_f("${filter}")(${exp})`
} else { // 如果不包含,说明过滤器并没有传递其他参数
// 针对包含(的情况,需要先从参数filter中将过滤器名和过滤器参数解析出来,而字符(的左边是过滤器名,右边是参数
const name = filter.slice(0, i)
const args = filter.slice(i + 1)
// 解析出来的参数右边多了一个小括号),在拼接字符串时需要去掉右边的小括号
return `_f("${name}")(${exp},${args}`
}
}
// 测试
parseFilters(`message | capitalize`)
// _f("capitalize")(message)
parseFilters(`message | filterA | filterB`)
// _f("filterB")(_f("filterA")(message))
parseFilters(`message | filterA('arg1', arg2)`)
// _f("filterA")(message,'arg1', arg2)
过滤器的原理是:在编译阶段将过滤器编译成函数调用,串联的过滤器编译后是一个嵌套的函数调用,前一个过滤器函数的执行结果是后一个过滤器函数的参数。
编译后的 _f函数是resolveFilter函数的别名,resolveFilter函数的作用是找到对应的过滤器并返回。