使用如下例子,说明 filter 作为 html 属性一部分或元素文本一部分的解析,以及 vue 对 filter 解析过程的详细实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <main id ="app" > <span :data-filter ="price|decimal(3)|format-unit|test" > {{price|decimal(3)|format-unit|test}}</span > </main > <script > const vm = new Vue ({ created ( ) { this .methodA (); }, data : { curcoder : { name : 'isaac' , position : 'fe' , email : 'isaacgun@outlook.com' }, price : 100 }, filters : { decimal (val, count = 2 ) { if (Number .isNaN (val)) { return val; } if (Number .isNaN (count) || count < 0 ) { count = 0 ; } return val.toFixed (count); }, formatUnit (val ) { return ['¥' , val].join ('' ); }, Test (val) { console .log (334 , this ); return val; } }, methods : { methodA ( ) { const { decimal } = this .$options .filters ; const num = decimal (120 , 4 ); console .log (num); } } }).$mount('#app' ); console .log ('vm:' , vm); </script >
filter 的三种调用方式 回到顶部
在双花括号中使用:<span>{{price|unit}}</span>,在解析模板阶段,使用 parseText 进行解析; 在 v-bind 中使用:<span :data-format-price="price|unit"></span>,在解析模板阶段,使用 processAttrs 进行解析; 在钩子或回调函数中使用:this.$options.filters.unit(this.price)。 在 parseText 中解析
path: vue/src/compiler/parser/text-parser.js:20
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g export function parseText ( text : string , delimiters ?: [string , string ] ): TextParseResult | void { const tagRE = delimiters ? buildRegex (delimiters) : defaultTagRE if (!tagRE.test (text)) { return } while ((match = tagRE.exec (text))) { log (match[1 ]); const exp = parseFilters (match[1 ].trim ()) } }
在 processAttrs 中解析
path: vue/src/compiler/parser/index.js:765
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function processAttrs (el ) { for (i = 0 , l = list.length ; i < l; i++) { name = rawName = list[i].name value = list[i].value if (dirRE.test (name)) { if (bindRE.test (name)) { name = name.replace (bindRE, '' ) log ('value:' , value) value = parseFilters (value) } } } }
可以看到,以上两种方式去解析 filter 文本,最后都是调用 parseFilters 对 filter 文本进行解析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 export function parseFilters (exp : string ): string { if (filters) { for (i = 0 ; i < filters.length ; i++) { expression = wrapFilter (expression, filters[i]) } } log (expression); return expression } function wrapFilter (exp : string , filter : string ): string { 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 !== ')' ? ',' + args : args} ` } }
filter 文本最后解析完还是文本!是一串有函数和参数组成的字符串,其中比较突出的就是 _f! 从 _f("decimal")(price,3) 就可以大致推断 _f 是一个工厂函数,用来生产 filter 函数,_f("decimal") 应该就是获取 decimal 过滤器,那么大概 _f("decimal")(price,3) 就是,调用 decimal 过滤器,传入参数 this.price,3!
为什么最后解析出的只是一段函数调用的文本?
因为 vue 的视图渲染分成两步:a. 解析视图模板,生成用于渲染整个视图的函数文本;b. 将函数文本作为视图订阅器(render-watcher)的 getter(用于获取watcher的值,watcher.value)。
可以直接打印一下例子中 render-watcher 的文本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 export const createCompiler = createCompilerCreator (function baseCompile ( template : string , options : CompilerOptions ): CompiledResult { const ast = parse (template.trim (), options) if (options.optimize !== false ) { optimize (ast, options) } const code = generate (ast, options) log (code.render ); return { ast, render : code.render , staticRenderFns : code.staticRenderFns } })
1 2 3 4 5 # output: with(this){return _c('main',{attrs:{"id":"app"}},[_c('span',{attrs:{ "data-filter":_f("test")(_f("format-unit")(_f("decimal")(price,3))) }},[_v(_s(_f("test")(_f("format-unit")(_f("decimal")(price,3)))))])])}
可以看见 _f("test")(_f("format-unit")(_f("decimal")(price,3))) 就被包含在其中!
_f 是什么?回到顶部
上面推测 _f 是生产过滤器的工厂,具体看下这个 _f 是怎么来的!
全局搜索 ._f,可以在 vue/src/core/instance/render-helpers/index.js 找到下面的代码:
1 2 3 4 5 6 7 import { resolveFilter } from './resolve-filter' export function installRenderHelpers (target : any ) { target._f = resolveFilter }
向上回溯去找 installRenderHelpers 在哪里被调用!可以找到 renderMixin:
1 2 3 4 export function renderMixin (Vue : Class <Component > ) { installRenderHelpers (Vue .prototype ) }
在 vue/src/core/instance/render-helpers/index.js 中向下寻找 resolveFilter 的本质逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import { identity, resolveAsset } from 'core/util/index' export function resolveFilter (id : string ): Function { return resolveAsset (this .$options , 'filters' , id, true ) || identity } export function resolveAsset ( options : Object , type : string , id : string , warnMissing ?: boolean ): any { if (typeof id !== 'string' ) { return } const assets = options[type ] 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 }
再看 _f("decimal"),即是 resolveFilter ("decimal")
1 2 resolveAsset (this .$options , 'filters' , 'decimal' , true );
那么对于 filter 来说,xx 的几个参数的意思:
1 2 3 4 5 6 function resolveAsset ( options : Object , type : string , id : string , warnMissing ?: boolean )
那么 const assets = options[type] 中的 assets 就是 filters,return assets[id] 就是返回我们自己定义的 filter-callback!
_f("decimal") 就返回 decimal 过滤器回调函数!
从上面 resolveAsset 的实现,可以看出,filter 的使用方式可以兼容以下几种情况:
正常调用,{{price|formatUnit}}; 中划线调用,单用驼峰法定义 filter,{{price|format-unit}}; 小写开头调用,但定义时用开头大写,{{price|test}},test 对使用 Test 定义的 filter 有效; 自然回溯原型链,但对于 filter 作用不大,除非是与原型链上属性或函数同名。 整个流程下来,可以发现:a. filter-callback 没有使用 bind 绑定上下文;b. 没有直接挂在在 vue 实例上。
这也是有别于 methods 的不同,看 methods 的初始化就知道:
1 2 3 4 5 6 7 function initMethods (vm : Component , methods : Object ) { const props = vm.$options .props for (const key in methods) { vm[key] = typeof methods[key] !== 'function' ? noop : bind (methods[key], vm) } }
filter 的特点:
filter 不会被代理到vm实例上; filter 不会绑定vm作为上下文。 总结 回到顶部
filter兼容的调用方式
正常调用,{{price|formatUnit}}; 中划线调用,单用驼峰法定义 filter,{{price|format-unit}}; 小写开头调用,但定义时用开头大写,{{price|test}},test 对使用 Test 定义的 filter 有效; 自然回溯原型链,但对于 filter 作用不大,除非是与原型链上属性或函数同名! filter 的特点
filter不会被代理到vm实例上; filter不会绑定vm作为上下文。