第十六章:过滤器的奥秘

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开发应用时,过滤器是一个常用的功能,用于格式化文本。

  用法:

    1. 基本用法
    2. 串联
    3. 接收参数

过滤器的原理:在编译阶段将过滤器编译成函数调用,串联的过滤器编译后是一个嵌套的函数调用,前一个过滤器函数的执行结果是后一个过滤器函数的参数。

编译后的_f函数是resolveFilter函数的别名,resolveFilter函数的作用是找到对应的过滤器并返回。

 模板编译过程中过滤器是如何被编译成过滤器函数调用的:1解析,2拼接字符串

posted @ 2021-05-12 14:27  小那  阅读(257)  评论(0编辑  收藏  举报