Vue2源码-过滤器篇

461 阅读3分钟

过滤器篇

个人更喜欢使用computed,而且在Vue3中filters也是去掉了

使用方式

有两种使用的方式,下面两种

!-- 在双花括号中 -->
{{ 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)
})

工作原理

下面代码其实就是把存储的函数,拿出来

定义位于源码的 src/core/instance/render-helpers/

关键函数 resolveFilter

// resolve-filter.js
import {
  identity,
  resolveAsset
} from "core/util/index";

export function resolveFilter(id) {
    /* 调用resolveAsset 获取返回值 或者 返回identity */
  return resolveAsset(this.$options, "filters", id, true) || identity;
}
/**
 * Return same value
 */
export const identity = (_) => _;
/* 
  调用该函数时传入了 4 个参数,
  分别是当前实例的 $options 属性, type 为 filters , 
  id 为当前过滤器的 id
*/
export function resolveAsset (
  options: Object,
  type: string,
  id: string,
  warnMissing?: boolean
): any {
  
  /* ID必须是一个字符串 */
  if (typeof id !== 'string') {
    return
  }
  /* 
    获取type对应的对象
  */
  const assets = options[type]

  // check local registration variations first
  /* 
    如果有这个属性的话,直接返回值
  */
  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]
  // fallback to prototype chain
  /* 
    再从原型链中查找
    如果还是找不到,就抛出警告
   */
  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 函数其实就是 resolveFilter 函数的别名,在 resolveFilter 函数内部是根据过滤器 id 从当前实例的 $options 中的 filters 属性中获取到对应的过滤器函数,在之后执行渲染函数的时候就会执行获取到的过滤器函数。

解析过滤器

下面代码白话就是把拿出来的函数来调用

使用的时候,有这两种方式

<!-- 在双花括号中 -->
{{ message | capitalize }}

<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
function processAttrs (el) {
    // 省略无关代码...
    if (bindRE.test(name)) { // v-bind
        // 省略无关代码...
        value = parseFilters(value)
        // 省略无关代码...
    }
    // 省略无关代码...
}
export function parseText (text,delimiters){
    // 省略无关代码...
    const exp = parseFilters(match[1].trim())
    // 省略无关代码...
}

parseFilters函数分析

parseFilters函数的定义位于源码的src/complier/parser/filter-parser.js文件中

该函数的作用的是将传入的形如'message | capitalize'这样的过滤器字符串转化成_f("capitalize")(message)

/* 

  该函数的作用的是将传入的
  形如'message | capitalize'这样的过滤器字符串转化成_f("capitalize")(message)
*/
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的每一个字符
  */
  for (i = 0; i < exp.length; i++) {
    prev = c
    c = exp.charCodeAt(i)
    if (inSingle) {
      if (c === 0x27 && prev !== 0x5C) inSingle = false
    } else if (inDouble) {
      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 && // pipe
      exp.charCodeAt(i + 1) !== 0x7C &&
      exp.charCodeAt(i - 1) !== 0x7C &&
      !curly && !square && !paren
    ) {
      if (expression === undefined) {
        // first filter, end of expression
        lastFilterIndex = i + 1
        expression = exp.slice(0, i).trim()
      } else {
        pushFilter()
      }
    } else {
      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
        }
        if (!p || !validDivisionCharRE.test(p)) {
          inRegex = true
        }
      }
    }
  }

  if (expression === undefined) {
    expression = exp.slice(0, i).trim()
  } else if (lastFilterIndex !== 0) {
    pushFilter()
  }

  function pushFilter () {
    (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim())
    lastFilterIndex = i + 1
  }

  if (filters) {
    for (i = 0; i < filters.length; i++) {
      expression = wrapFilter(expression, filters[i])
    }
  }

  return expression
}

该函数接收一个形如'message | capitalize'这样的过滤器字符串作为,最终将其转化成_f("capitalize")(message)输出。在parseFilters函数的内部是通过遍历传入的过滤器字符串每一个字符,根据每一个字符是否是一些特殊的字符从而作出不同的处理,最终,从传入的过滤器字符串中解析出待处理的表达式expression和所有的过滤器filters数组。

最后,将解析得到的expressionfilters数组通过调用wrapFilter函数将其构造成_f函数调用字符串。