Vue源码阅读--过滤器
栏目: JavaScript · 发布时间: 6年前
内容简介:过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示:我们发现对于过滤器的使用方式有两种:在parse处理开始节点的processAttrs() 中发现了 通过bindRE.test(name)去匹配响应式的属性,然后通过 parseFilters(value) 去解析值中的过滤器。
过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示:
<!-- 在双花括号中 --> {{ message | capitalize }} <!-- 在 `v-bind` 中 --> <div v-bind:id="rawId | formatId"></div> 复制代码
创建过滤器的方式
- Vue.filter('id',function(){}) 全局过滤器定义
- 组件中 filters : { 'id' : function(){} } 组件内部过滤器
源码分析
一、编译阶段
parse阶段
我们发现对于过滤器的使用方式有两种:
- 在属性中 v-bind:id="xxx | filterA"
- 在文本双花括号插值中 {{xxx | filterA | filterB(arg1,arg2)}}
1. 属性中 v-bind:id="xxx | filterA"
在parse处理开始节点的processAttrs() 中发现了 通过bindRE.test(name)去匹配响应式的属性,然后通过 parseFilters(value) 去解析值中的过滤器。
if (bindRE.test(name)) { // v-bind // 获取属性的名称 移除 : | v-bind: name = name.replace(bindRE, '') // 处理value 解析成正确的value value = parseFilters(value) isProp = false } 复制代码
compiler\parser\filter-parser.js
/** * 表达式中的过滤器解析 方法 * @param {*} exp */ export function parseFilters(exp: string): string { // 是否在 ''中 let inSingle = false // 是否在 "" 中 let inDouble = false // 是否在 `` let inTemplateString = false // 是否在 正则 \\ 中 let inRegex = false // 是否在 {{ 中发现一个 culy加1 然后发现一个 } culy减1 直到culy为0 说明 { .. }闭合 let curly = 0 // 跟{{ 一样 有一个 [ 加1 有一个 ] 减1 let square = 0 // 跟{{ 一样 有一个 ( 加1 有一个 ) 减1 let paren = 0 // let lastFilterIndex = 0 let c, prev, i, expression, filters 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 ) { /* 如果前面没有表达式那么说明这是第一个 管道符号 "|" 再次遇到 | 因为前面 expression = 'message ' 执行 pushFilter() */ 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() } // 获取当前过滤器的 并将其存储在filters 数组中 // filters = [ 'filterA' , 'filterB'] 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 } 复制代码
解析过滤器的方法其实很简单:
- 将属性的值从前往后开始一个一个匹配,关键符号 : "|" 并排除 ""、 ''、 ``、 //、 || (字符串、正则)中的管道符号 '|' 。
如:
<div v-bind:id="message + 'xxx|bbb' + (a||b) + `cccc` | filterA | filterB(arg1,arg2)"></div> 复制代码
字符一个一个往后匹配 如果发现 ` " ' 说明在字符串中,那么直到找到下一个匹配的才结束, /一样 同时 匹配 () {} [] 这些需要两边相等闭合 那么 | 才有效, 最后一个条件排除 || 即可
- 所以上面直到遇到 第一个正确的 | ,那么前面的表达式 并存储在 expression 中,后面继续匹配再次遇到 | ,那么此时 expression有值, 说明这不是第一个过滤器 pushFilter() 去处理上一个过滤器
/** 生成过滤器的 表达式字符串 如上面的 exp = message filters = ['filterA','filterB(arg1,arg2)'] 第一步 以exp 为入参 生成 filterA 的过滤器表达式字符串 _f("filterA")(message) 第二步 以第一步字符串作为入参 生成第二个过滤器的表达式字符串 _f("filterB")(_f("filterA")(message),arg1,arg2) => _f("filterB")(_f("filterA")(message),arg1,arg2) * @param {string} exp 上一个过滤器的值 没有就是 表达式的值 * @param {string} filter * @returns {string} */ function wrapFilter(exp: string, filter: string): string { // 判断是否存在入参, 即 'filterB(arg1,arg2)' const i = filter.indexOf('(') if (i < 0) { // 如果不是 直接生成 "_f("filterA")(message)" // _f: resolveFilter return `_f("${filter}")(${exp})` } else { // 过滤器名称 const name = filter.slice(0, i) // 过滤器自定义入参 const args = filter.slice(i + 1) // 生成 "_f("filterB")(message,arg1,arg2)" return `_f("${name}")(${exp}${args !== ')' ? ',' + args : args}` } } 复制代码
此时 exp = message + 'xxx|bbb' + (a||b) + cccc
, filter = filterA
- 继续判断 过滤器是否存在 (), 此时不存在, 那么filter就是名称 第一个入参就是前面的 exp。
生成
"_f("filterA")(message + 'xxx|bbb' + (a||b) + `cccc`)" 复制代码
- 以前面的结果为exp , 发现存在 ( , 然后生成
"_f("filterB")(_f("filterA")(message + 'xxx|bbb' + (a||b) + `cccc`),arg1,arg2)" 复制代码
2. 文本双花括号插值中 {{ message | capitalize }}
文本的处理是在 parse中的chars()方法 其中存在一个解析 {{}} 的方法 parseText()
export function parseText( text: string, delimiters ? : [string, string] ): TextParseResult | void { // 处理 文本内容 如: // {{obj.name}} is {{obj.job}} while ((match = tagRE.exec(text))) { // ' {{obj.name}} is {{obj.job}} ' => [ 0: '{{obj.name}}' , 1: 'obj.name' ,index : 1, input: ' {{obj.name}} is {{obj.job}} '] // match.index 获取当前匹配的 开始下标 index = match.index // push text token // 如果 {{ }}的前面存在 静态的文本 如 (空格..{{xx}} xxx {{}})那么需要将这些静态文本保存 if (index > lastIndex) { rawTokens.push(tokenValue = text.slice(lastIndex, index)) // 将静态文本保存在 tokens tokens.push(JSON.stringify(tokenValue)) } // tag token // 解析过滤器 const exp = parseFilters(match[1].trim()) //生成当前参数在Vue render中获取响应式数据的方法 _s('obj.name') => this['obj.name'] tokens.push(`_s(${exp})`) } } 复制代码
发现 其也是通过const exp = parseFilters(match[1].trim()) 去处理 {{}}中的额过滤器。
render阶段
我们发现在编译节点如果遇到过滤器 会将其编译成 _f(){}的表达式
"_f("filterB")(_f("filterA")(message + 'xxx|bbb' + (a||b) + `cccc`),arg1,arg2)" 复制代码
core\instance\render-helpers\resolve-filter.js
/** * Runtime helper for resolving filters */ export function resolveFilter (id: string): Function { return resolveAsset(this.$options, 'filters', id, true) || identity } 复制代码
其还是通过resolveAsset去获取 vm.$options的filters中相同的过滤器
然后将 _f("filterA")(message + 'xxx|bbb' + (a||b) + cccc
),arg1,arg2 作为入参。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Spring Security系列之核心过滤器源码分析(四)
- 网关 Spring-Cloud-Gateway 源码解析 —— 过滤器 (4.3) 之 RouteToRequestUrlFilter
- 网关 Spring-Cloud-Gateway 源码解析 —— 过滤器 (4.8) 之 WebClientHttpRoutingFilter
- 网关 Spring-Cloud-Gateway 源码解析 —— 过滤器 (4.5) 之 ForwardRoutingFilter
- 网关 Spring-Cloud-Gateway 源码解析 —— 过滤器 (4.6) 之 WebSocketRoutingFilter
- 网关 Spring-Cloud-Gateway 源码解析 —— 过滤器 (4.7) 之 NettyRoutingFilter
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。