Vue filter详解

Vue filter源码详解

1.解析表达式

reportDate | DFormat('YYYY-MM-DD') | SDefault为例。

parseFilters 解析函数位于node_modules/vue/src/compiler/parser/filter-parser.js。主要作用是将表达式转换为可立即执行的函数字符串。

//匹配任意非空字符、)、.、+、-、_、$、]
//排除一些例如a++,a--,../path,(a+b)之类的影响
const validDivisionCharRE = /[\w).+\-_$\]]/

export function parseFilters(exp: string): string {
    let inSingle = false    //当前字符是否在单引号中
    let inDouble = false    //当前字符是否在双引号中
    let inTemplateString = false    //当前字符是否在es6中的`模板标识符中
    let inRegex = false     //当前字符是否在正在表达式/中
    let curly = 0           //匹配到{时+1,匹配到}时-1
    let square = 0          //匹配到[时+1,匹配到]时-1
    let paren = 0           //匹配到(时+1,匹配到)时-1
    let lastFilterIndex = 0 //最后一个过滤器的起始坐标
    //c当前字符、prev上次遍历的字符、i遍历下标、expression表达式、filters过滤器
    let c, prev, i, expression, filters

    for (i = 0; i < exp.length; i++) {
        //新一轮遍历时,修改上一轮循环的字符为遍历完成的最后一个字符
        prev = c
        //返回指定下标的Unicode编码
        let a = exp.charAt(i)
        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) {
            //当前字符不是`,并且上一个字符不是\,则`es6模板部分结束
            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
                //第一次解析filter,记录最后一个filter的起始位置为|后的一位
                lastFilterIndex = i + 1
                //提取|前的部分作为表达式,即提取过滤器的第一个参数
                expression = exp.slice(0, i).trim()
            } else {
                //将解析到的filter放入预置的filters数组中
                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
                //找到第一个不为空的字符p
                for (; j >= 0; j--) {
                    p = exp.charAt(j)
                    if (p !== ' ') break
                }
                //如果p存在且通过了特殊字符校验,那么则认为之后的/之后的内容为正则表达式内容
                if (!p || !validDivisionCharRE.test(p)) {
                    inRegex = true
                }
            }
        }
    }

    
    //遍历完成后,表达式即为过滤器第一个参数,并且获得了最后一个过滤器的起始坐标lastFilterIndex
    if (expression === undefined) {
        expression = exp.slice(0, i).trim()
    } else if (lastFilterIndex !== 0) {
        //如果存在过滤器则进入这一步骤,此时除了最后一个过滤器,
        //其他过滤器均被解析放入filters中了,所以将最后一个过滤器也放入
        pushFilter()
    }

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

    if (filters) {
        //遍历已经解析出来的filters数组,依次处理每个filter
        for (i = 0; i < filters.length; i++) {
            expression = wrapFilter(expression, filters[i])
        }
    }

    //最后返回一个可执行的函数字符串
    return expression
}

function wrapFilter(exp: string, filter: string): string {
    const i = filter.indexOf('(')
    //不带参数的filter
    if (i < 0) {
        // _f: resolveFilter
        return `_f("${filter}")(${exp})`
    } else {
        //带参数的filter
        const name = filter.slice(0, i)
        const args = filter.slice(i + 1)
        //将第一个表达式作为过滤器的第一个参数,其他参数依次拼接
        return `_f("${name}")(${exp}${args !== ')' ? ',' + args : args}`
    }
}

解析后的结果为:_f("defaultStr")(_f("DFormat")(reportDate,'YYYY-MM-DD'))

_f是一个注册函数,用于注册我们书写的过滤器方法。

2.注册过滤器(resolveFilter)

2.1Vue初始化

Vue初始化后会调用initGlobalAPI 添加静态方法,位于node_modules/vue/src/core/global-api/index.js

/* @flow */

import config from '../config'
import { initUse } from './use'
import { initMixin } from './mixin'
import { initExtend } from './extend'
import { initAssetRegisters } from './assets'
import { set, del } from '../observer/index'
import { ASSET_TYPES } from 'shared/constants'
import builtInComponents from '../components/index'
import { observe } from 'core/observer/index'

import {
  warn,
  extend,
  nextTick,
  mergeOptions,
  defineReactive
} from '../util/index'

export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }

  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {//在这里添加filter、component、directive中的方法
    Vue.options[type + 's'] = Object.create(null)
  })

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue

  extend(Vue.options.components, builtInComponents)

  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)//初始化各组件方法
}

export const ASSET_TYPES = ['component', 'directive', 'filter']

这里初始化为Vue注册了config对象和部分插件方法。

initAssetRegisters位于node_modules/vue/src/core/global-api/assets.js

/* @flow */

import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '../util/index'

export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

当type为filter是即注册过滤器方法,id即为过滤器方法名,definition为过滤器方法。

注册过后即可使用如下方式定义全局filter:

Vue.filter('format', function(name) {
  return Hello, ${name} !
})

需注意,filter方法并不是在实例化的时候才创建的,而是实例化之前即创建好了,所以filter内的this无法获取到vue实例。

2.2初始化绑定_f

installRenderHelpers位于node_modules/vue/src/core/instance/render-helpers/index.js

/* @flow */

import { toNumber, toString, looseEqual, looseIndexOf } from 'shared/util'
import { createTextVNode, createEmptyVNode } from 'core/vdom/vnode'
import { renderList } from './render-list'
import { renderSlot } from './render-slot'
import { resolveFilter } from './resolve-filter'
import { checkKeyCodes } from './check-keycodes'
import { bindObjectProps } from './bind-object-props'
import { renderStatic, markOnce } from './render-static'
import { bindObjectListeners } from './bind-object-listeners'
import { resolveScopedSlots } from './resolve-scoped-slots'
import { bindDynamicKeys, prependModifier } from './bind-dynamic-keys'

export function installRenderHelpers (target: any) {
  target._o = markOnce
  target._n = toNumber
  target._s = toString
  target._l = renderList
  target._t = renderSlot
  target._q = looseEqual
  target._i = looseIndexOf
  target._m = renderStatic
  target._f = resolveFilter		//过滤器注册函数
  target._k = checkKeyCodes
  target._b = bindObjectProps
  target._v = createTextVNode
  target._e = createEmptyVNode
  target._u = resolveScopedSlots
  target._g = bindObjectListeners
  target._d = bindDynamicKeys
  target._p = prependModifier
}

_f是获取具体过滤器的函数,其绑定在Vue原型上。

2.3调用具体的filter

resolveFilter位于node_modules/vue/src/core/instance/render-helpers/resolve-filter.js

//原值返回函数
export const identity = (_: any) = _

//注册filter
function resolveFilter(id) {    
    return resolveAsset(
        //这里的this是vm,resolveFilter是注册在Vue原型上的方法
        this.$options, 'filters', id, true
    ) || identity
}

resolveAsset位于node_modules/vue/src/core/util/options.js

//执行具体的指令,过滤器的type为filters,返回一个函数
export function resolveAsset (
  options: Object,
  type: string,
  id: string,
  warnMissing?: boolean
): any {
  /* istanbul ignore if */
  if (typeof id !== 'string') {
    return
  }
  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
}

3.流程图

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值