浅谈Vue2选项--filters

160 阅读2分钟

过滤器的概念

  • 过滤器是输送介质管道上不可缺少的装置,就是把不必要的东西过滤掉
  • 实质: 不改变原始数据,只对原数据进行处理,返回过滤后的数据
  • 本质: 函数
  • 用途: 常用于文本的格式化
  • 用处: 插值表达式v-bind属性绑定
 <!-- 插值表达式 -->
 {{ message | capitalize }}
 ​
 <!-- v-bind属性绑定 -->
 <div v-bind:id="message | capitalize"></div>

注意:

  • 过滤器函数一定要有return值
  • 过滤器的第一个形参就是管道符 "|" 前面待处理的值

局部挂载

  • 局部过滤器定义到filters节点下只能在当前组件实例所控制的el区域内使用
 <p>message的值是:{{ message | capi }}</p>
 data(){
   return { message:'hello filters' }
 },
 ​
 filters:{
   capi(payload){
     const first = payload.charAt(0).toUpperCase()
     const other = payload.slice(1)
     // 一定要有返回值
     return first + other
   }
 }

1666516759539.png

全局挂载

  • 在多个组件实例之间共享的过滤器,独立于每个组件实例之外

  • 全局挂载需要使用 Vue.filter() 方法

    • 参数①: 过滤器的名字
    • 参数②: 过滤器的处理函数
 /*
   filter/index.js
   定义全局过滤器
  */
 export default function fix2Num(payload){
   return Number( payload || 0 ).toFixed(2)
 }
 /*
   main.js
   挂载到Vue实例中
 */ 
 import * as filters from '@/filters';
 ​
 Object.keys(filters).forEach((key) => {
   Vue.filter(key, filters[key]);
 });
 <!-- 在模板中使用 -->
 <h1>message的值是:{{ num | fix2Num }}</h1>

1666519440194.png

注意:当全局过滤器和局部过滤器重名时,会采用局部过滤器

串联过滤器

  • 过滤器可链式调用
  • 即已经过滤完成的结果,继续传给下一个过滤器
 <h1>num:{{ price | fixed2Num | toRMB }}</h1>
 filters:{
   fixed2Num(payload){
     return Number(payload).toFixed(2)
   },
   toRMB(payload) {
     return '¥' + payload
   },
 }

1666526397086.png

过滤器传参

  • 过滤器本质是一个函数,可接受参数
  • 函数第一个参数永远是原数据,后续是自定义参数
 <h1>num:{{ price | fixed2Num('rmb',3) }}</h1>
  • 剩余参数 rest 写法
 filters:{
   fixed2Num(payload,...args){
     return `${args[0]==='rmb'?'¥':'$'}${Number(payload).toFixed(args[1])}`
   },
 }
  • 形参写法
 filters:{
   fixed2Num(payload,type = 'rmb',num = 2){
     return `${type==='rmb'?'¥':'$'}${Number(payload).toFixed(num)}`
   },
 }

1666526963568.png

过滤器原理

  • 在模板中,过滤器写法如下:
 <!-- 普通使用 -->
 <h1>num:{{ price | fixed2Num }}</h1>
 ​
 <!-- 串联调用 -->
 <h1>num:{{ price | fixed2Num | toRMB }}</h1>
 ​
 <!-- 传参调用 -->
 <h1>num:{{ price | fixed2Num('rmb',3) }}</h1>
  • 经过模板编译之后,转变成以下形式
 // 普通使用
 _s(_f('fixed2Num')(price))
 ​
 // 串联调用
 _s(_f('toRMB')(_f('fixed2Num')(price)))
 ​
 // 传参调用
 _s(_f('fixed2Num')(price,'rmb',3))

_f()函数是什么?

  • _f函数是 resolveFilter的别名
  • 作用: 找到注册的 fixed2Num 过滤器,并且返回出去

_s()函数是什么?

  • toString 函数的别名

1666527360359.png

  • 过滤器执行原理:

    1. 执行 fixed2Num 过滤器函数并将 price当参数传入
    2. 拿到参数后调用 toString 函数
    3. 最终 toString 函数的执行结果保存到 VNode 的text属性中渲染成视图

resolveFilter函数

  • 作用就是找到过滤器
  • 通过 resolveAsset 函数能找到就返回过滤器,找不到就返回 identity
 // resolveFilter函数
 function resolveFilter (id) {
   return resolveAsset(this.$options, 'filters', id, true) || identity
 }

identity是什么?

  • 函数,传入什么值就返回什么值

1666528403692.png

resolveAsset函数

  • 作用是找到模板中写的过滤器
  • 源码如下:
 /*
   options:当前组件实例
   type:字符串`'filters'
   id:过滤器函数名
 */
 function resolveAsset (options, type, id, warnMissing) {
 ​
   if (typeof id !== 'string') return 
   var assets = options[type];
   
   if (hasOwn(assets, id)) { return assets[id] }
   var camelizedId = camelize(id);
   if (hasOwn(assets, camelizedId)) { return assets[camelizedId] }
   var PascalCaseId = capitalize(camelizedId);
   if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId] }
   
   var 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
 }
  1. 判断 id 是否为字符串,如果不是则直接返回 undefined,然后执行 identitiy 函数
  2. 声明变量 assets 保存 options['filters'],过滤器的集合
  3. 判断 assets 是否含有 id 属性的值,有则直接返回 assets[id] (其实就是filters)
  4. 若没有则通过 camelize 函数将 id 驼峰化后再判断自身含有id属性
  5. 若还没有则通过 capitalizeid 首字母大写,继续判断
  6. 最后返回三者其中之一
  7. 如果是生产环境,并且允许报警告,而且上述并无返回,则直接报警告没找到该过滤器函数

过滤器的解析

  • 通过 parseFilters 函数, 专门用来解析过滤器
  • Vuesrc/compiler/parser/filter-parser.js

1666529439456.png

  • 解析原理:

    • parseFiltersh函数:拿到过滤器名字和参数,循环调用 wrapFilter 函数
    • 过滤器名称字符串'filter',传入 wrapFilter 函数
     // 核心代码
     for (i = 0; i < filters.length; i++) {
       expression = wrapFilter(expression, filters[i])
     }
    
    • wrapFilter:通过indexOf()判断是否有参数,返回的拼接字符串
     function wrapFilter (exp: string, filter: string): string {
       const i = filter.indexOf('(')
       if (i < 0) {
         // _f: resolveFilter
         return `_f("${filter}")(${exp})`
       } else {
         const name = filter.slice(0, i)
         const args = filter.slice(i + 1)
         return `_f("${name}")(${exp}${args !== ')' ? ',' + args : args}`
       }
     }
    

过滤器原理主要分为两个阶段:

  • 编译: 编译阶段将带有过滤器的模板编译成函数调用
  • 执行: 通过 _f() 查询挂载的过滤器,执行过滤器函数

参考: juejin.cn/post/684490…