用於一些常見的文本格式化html
過濾器能夠用在兩個地方:雙花括號插值和 v-bind 表達式 (後者從 2.1.0+ 開始支持)。過濾器應該被添加在 JavaScript 表達式的尾部,由「管道」符號指示:vue
<!-- 在雙花括號中 -->
{{ message | capitalize }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
複製代碼
咱們發現對於過濾器的使用方式有兩種:git
在parse處理開始節點的processAttrs() 中發現了 經過bindRE.test(name)去匹配響應式的屬性,而後經過 parseFilters(value) 去解析值中的過濾器。github
if (bindRE.test(name)) { // v-bind
// 獲取屬性的名稱 移除 : | v-bind:
name = name.replace(bindRE, '')
// 處理value 解析成正確的value
value = parseFilters(value)
isProp = false
}
複製代碼
/** * 表達式中的過濾器解析 方法 * @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
}
複製代碼
解析過濾器的方法其實很簡單:正則表達式
如:express
<div v-bind:id="message + 'xxx|bbb' + (a||b) + `cccc` | filterA | filterB(arg1,arg2)"></div>
複製代碼
字符一個一個日後匹配 若是發現 ` " ' 說明在字符串中,那麼直到找到下一個匹配的才結束, /同樣 同時 匹配 () {} [] 這些須要兩邊相等閉合 那麼 | 纔有效, 最後一個條件排除 || 便可api
/** 生成過濾器的 表達式字符串 如上面的 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數組
生成curl
"_f("filterA")(message + 'xxx|bbb' + (a||b) + `cccc`)"
複製代碼
"_f("filterB")(_f("filterA")(message + 'xxx|bbb' + (a||b) + `cccc`),arg1,arg2)"
複製代碼
文本的處理是在 parse中的chars()方法 其中存在一個解析 {{}} 的方法 parseText()ide
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()) 去處理 {{}}中的額過濾器。
咱們發如今編譯節點若是遇到過濾器 會將其編譯成 _f(){}的表達式
"_f("filterB")(_f("filterA")(message + 'xxx|bbb' + (a||b) + `cccc`),arg1,arg2)"
複製代碼
/** * 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 做爲入參。
更多博客,查看 github.com/imisou/vue.…
做者:HardStone