过滤器的原理
{{message | capitalize}}
这个过滤器会被模板编译成下面的样子
_s(_f("capitalize")(message))
_f函数是resolveFilter的别名。作用是从this.$options.filters中找到注册的过滤器并返回
this.$optioins.filters['capitalize']就是我们这次的capitalize过滤器函数
filters:{
capitalize: function(value){
if(!value)return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
因此 _f("capitalize")(message)其实就是执行过滤器并传递了参数。
_s函数其实是toString函数的别名。
export function toString (val: any): string {
return val == null
? ''
: Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
? JSON.stringify(val, null, 2)
: String(val)
}
简单来说就是执行 过滤器函数 并传递 参数, 接着 capitalize 过滤器 处理后的结果 当做参数 传递给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: funtion(value, symbol = '~'){
return value + symbol
}
}
模板编译阶段会编译成下面的样子
_s(_f("suffix")(_f("capitalize")(message)))
过滤器接受参数
{{message | capitalize| suffix('!')}}
编译后
_s(_f("suffix")(_f("capitalize")(message), '!'))
resolveFilter 的原理
resolveFilter 其实就是 调用改函数查找过滤器,找到了返回,如果没找到,方法identity(该函数方法同参数的值)
// export const identity = (_: any) => _
export function resolveFilter(id: string): Function {
return resolveAsset(this.$options, 'filters', id, true) || identity
}
resolveAsset 函数可以查找 组件、指令、过滤器
// 可以 查找 组件、指令、过滤器
export function resolveAsset(
options: Object,
type: string,
id: string,
warnMissing ? : boolean
): any {
/* istanbul ignore if */
// id类型 必须是字符串类型
if (typeof id !== 'string') {
return
}
// 声明 assets 并将 options[typs]保存
const assets = options[type]
// 检查assets 自身是否有 id属性
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 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
}
解析过滤器
我们在知道过滤器内部如何执行之后,再来了解一下,过滤器语法是如何编译的。
vue.js内部 src\compiler\parser\filter-parser.js 就是专门用来 将模板解析成过滤器函数 调用表达式的。
/* @flow */
const validDivisionCharRE = /[\w).+\-_$\]]/
// "message | filterA | filterB(age)"
export function parseFilters(exp: string): string {
let inSingle = false // 单引号 '
let inDouble = false // 双引号 "
let inTemplateString = false // `符号
let inRegex = false // 正则符号 /
let curly = 0 // {}
let square = 0 // []
let paren = 0 // ()
let lastFilterIndex = 0
let c, prev, i, expression, filters
// 按字符串 遍历 exp 类似于 "message | filterA | filterB(age)"
for (i = 0; i < exp.length; i++) {
// 保存上一次 的 字符
prev = c
// 获取当前 字符 比如第一次 m
c = exp.charCodeAt(i)
// 如果 在 inSingle 为true 的情况下
if (inSingle) {
// 碰到 当前 c 为双引号,上一次字符 不是 \ 转义符 把 inSingle 置为 false
// 上一次碰到过 单引号 ,这次又碰到 单引号 。 把 标识符inSingle 改为false。 之后的字符不属于 单引号中了
if (c === 0x27 && prev !== 0x5C) inSingle = false
} else if (inDouble) {
// 碰到 当前 c 为双引号,上一次字符 不是 \ 转义符
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 && // 当前字符是 |
exp.charCodeAt(i + 1) !== 0x7C && // 下一个字符不是 |
exp.charCodeAt(i - 1) !== 0x7C && // 上一个字符也不是 |
!curly && !square && !paren // 不在 {} () [] 中
) {
// 第一次 expression 参数 不存在的情况下, 那么 | 前面的是 参数
if (expression === undefined) {
// first filter, end of expression
// lastFilterIndex 设置为下一个字符
lastFilterIndex = i + 1
// expression 为 0 到 当前位置 如 message
expression = exp.slice(0, i).trim()
} else {
// 不是第一次的情况,| 前面的是 filter方法。 保存当前的 filter方法 比如filterA
pushFilter()
}
} else {
// 上面情况都不符合的情况下 比如刚开始 c 为 "
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
}
// 如果这个字符不存在,前面没有其他字符 刚开始 正则
// 或者 是第二个正则符号 /, 且 挨着的第一个符号 不符合正则的规范
// 那么 设置 inRegex,是正则 开始
if (!p || !validDivisionCharRE.test(p)) {
inRegex = true
}
}
}
}
// 如果把 exp 循环完之后 没有 expression 比如没有 message。 (没有遇到|)
if (expression === undefined) {
// expression参数 就是 自己本身
expression = exp.slice(0, i).trim()
} else if (lastFilterIndex !== 0) {
// lastFilterIndex 不为0 。说明 前面遇到过 | 。 那么把最后的
// 循环 完之后 | 之后 的filter 保存 到filters 数组中
pushFilter()
}
// 获取 多个 filter 方法 ,并存入 filtes数组
function pushFilter() {
(filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim())
lastFilterIndex = i + 1
}
// 如果有filter方法 。遍历
if (filters) {
for (i = 0; i < filters.length; i++) {
// 分别调用 wrapFilter 传入上次处理后的expession作为参数,并传入filter方法
expression = wrapFilter(expression, filters[i])
}
}
// 返回最后的结果
return expression
}
function wrapFilter(exp: string, filter: string): string {
// filter中可能也有参数 比如 filterB(age)
const i = filter.indexOf('(')
if (i < 0) {
// 如果没有 (, 那么 没有参数 直接返回 过滤器表达式
// _f: resolveFilter
return `_f("${filter}")(${exp})`
} else {
// 如果有参数 获取 name 为filterB
// args 为 参数 age
const name = filter.slice(0, i)
const args = filter.slice(i + 1)
// 返回过滤器表达式
// 如果args 为 ) 说明 有() 当时没有参数
return `_f("${name}")(${exp}${args !== ')' ? ',' + args : args}`
}
}
总结: 过滤器除了基本使用方式外,还能让串联并接受参数。
过滤器的原理: 在编译阶段将过滤器编译成函数调用, 串联的过滤器编译后是一个嵌套的函数调用, 前一个过滤器函数的执行结果是后一个过滤器函数的参数。
编译后的_f函数是resolveFilter函数的别名,作用是找到对应的过滤器并返回。