0
点赞
收藏
分享

微信扫一扫

vue2-过滤器

香小蕉 2021-09-24 阅读 41
一点点

过滤器的原理

{{message | capitalize}}
这个过滤器会被模板编译成下面的样子
_s(_f("capitalize")(message))

_f函数是resolveFilter的别名。作用是从this.$options.filters中找到注册的过滤器并返回

this.$optioins.filters['capitalize']就是我们这次的capitalize过滤器函数

filters:{
  capitalize: function(value){
    if(!value)return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
  }
}

因此 _f("capitalize")(message)其实就是执行过滤器并传递了参数。

_s函数其实是toString函数的别名。

export function toString (val: any): string {
  return val == null
    ? ''
    : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
      ? JSON.stringify(val, null, 2)
      : String(val)
}

简单来说就是执行 过滤器函数 并传递 参数, 接着 capitalize 过滤器 处理后的结果 当做参数 传递给toString函数, toString函数执行后结果会保存到Vnode的text属性中。

串联过滤器

{{message | capitalize | suffix}}

本地 过滤器

filters:{
  capitalize: function(value){
    if(!value)return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
  },
  suffix: funtion(value, symbol = '~'){
    return value + symbol
  }
}

模板编译阶段会编译成下面的样子
_s(_f("suffix")(_f("capitalize")(message)))

过滤器接受参数
{{message | capitalize| suffix('!')}}
编译后
_s(_f("suffix")(_f("capitalize")(message), '!'))

resolveFilter 的原理

resolveFilter 其实就是 调用改函数查找过滤器,找到了返回,如果没找到,方法identity(该函数方法同参数的值)

// export const identity = (_: any) => _
export function resolveFilter(id: string): Function {
    return resolveAsset(this.$options, 'filters', id, true) || identity
}

resolveAsset 函数可以查找 组件、指令、过滤器

// 可以 查找 组件、指令、过滤器
export function resolveAsset(
    options: Object,
    type: string,
    id: string,
    warnMissing ? : boolean
): any {
    /* istanbul ignore if */
    // id类型 必须是字符串类型
    if (typeof id !== 'string') {
        return
    }
    // 声明 assets 并将 options[typs]保存
    const assets = options[type]
        // 检查assets 自身是否有 id属性
    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
}

解析过滤器

我们在知道过滤器内部如何执行之后,再来了解一下,过滤器语法是如何编译的。
vue.js内部 src\compiler\parser\filter-parser.js 就是专门用来 将模板解析成过滤器函数 调用表达式的。

/* @flow */

const validDivisionCharRE = /[\w).+\-_$\]]/

//  "message | filterA | filterB(age)"
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  类似于 "message | filterA | filterB(age)"
    for (i = 0; i < exp.length; i++) {
        // 保存上一次 的 字符
        prev = c
            // 获取当前 字符 比如第一次  m 
        c = exp.charCodeAt(i)

        // 如果 在 inSingle 为true 的情况下 
        if (inSingle) {
            //  碰到 当前 c 为双引号,上一次字符 不是 \ 转义符   把 inSingle 置为 false
            //  上一次碰到过 单引号 ,这次又碰到 单引号 。 把 标识符inSingle 改为false。 之后的字符不属于 单引号中了
            if (c === 0x27 && prev !== 0x5C) inSingle = false
        } else if (inDouble) {
            //  碰到 当前 c 为双引号,上一次字符 不是 \ 转义符
            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 && // 当前字符是  | 
            exp.charCodeAt(i + 1) !== 0x7C && // 下一个字符不是 |
            exp.charCodeAt(i - 1) !== 0x7C && // 上一个字符也不是 |
            !curly && !square && !paren // 不在 {} () [] 中
        ) {
            // 第一次 expression 参数 不存在的情况下, 那么 | 前面的是 参数
            if (expression === undefined) {
                // first filter, end of expression
                // lastFilterIndex 设置为下一个字符
                lastFilterIndex = i + 1
                    // expression 为 0 到 当前位置 如 message 
                expression = exp.slice(0, i).trim()
            } else {
                // 不是第一次的情况,| 前面的是 filter方法。  保存当前的 filter方法 比如filterA
                pushFilter()
            }
        } else {
            // 上面情况都不符合的情况下 比如刚开始 c 为 "
            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
                }
                // 如果这个字符不存在,前面没有其他字符 刚开始 正则
                // 或者 是第二个正则符号  /, 且 挨着的第一个符号 不符合正则的规范
                // 那么 设置 inRegex,是正则 开始
                if (!p || !validDivisionCharRE.test(p)) {
                    inRegex = true
                }
            }
        }
    }

    // 如果把 exp 循环完之后 没有 expression 比如没有 message。 (没有遇到|)
    if (expression === undefined) {
        // expression参数 就是 自己本身
        expression = exp.slice(0, i).trim()
    } else if (lastFilterIndex !== 0) {
        // lastFilterIndex 不为0 。说明 前面遇到过 | 。 那么把最后的 
        // 循环 完之后 | 之后 的filter 保存 到filters 数组中 
        pushFilter()
    }


    // 获取 多个 filter 方法 ,并存入 filtes数组 
    function pushFilter() {
        (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim())
        lastFilterIndex = i + 1
    }

    // 如果有filter方法 。遍历
    if (filters) {
        for (i = 0; i < filters.length; i++) {
            // 分别调用 wrapFilter 传入上次处理后的expession作为参数,并传入filter方法
            expression = wrapFilter(expression, filters[i])
        }
    }

    // 返回最后的结果
    return expression
}

function wrapFilter(exp: string, filter: string): string {
    // filter中可能也有参数 比如 filterB(age)
    const i = filter.indexOf('(')
    if (i < 0) {
        // 如果没有 (, 那么 没有参数 直接返回 过滤器表达式
        // _f: resolveFilter
        return `_f("${filter}")(${exp})`
    } else {
        // 如果有参数 获取 name 为filterB
        // args 为 参数 age 
        const name = filter.slice(0, i)
        const args = filter.slice(i + 1)
            // 返回过滤器表达式  
            // 如果args 为 )  说明 有() 当时没有参数
        return `_f("${name}")(${exp}${args !== ')' ? ',' + args : args}`
    }
}

总结: 过滤器除了基本使用方式外,还能让串联并接受参数。

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

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

举报

相关推荐

0 条评论