最近工做中遇到一個問題,就是 filter 中的函數沒法使用綁定在 Vue.prototype
的函數。都知道,在 created
、mounted
、methods
中, this
綁定的都是當前 Vue
實例。恰恰 filter
函數 this
指向的是 Window
?html
直接上例子:git
<div id="app">
{{ myArr | filtersOdd | filtersOverFour }}
</div>
複製代碼
Vue.filter('filtersOverFour', function (v) {
console.log('全局的filter的this:');
console.log(this);
return v.filter(item => item > 4);
});
new Vue({
el: '#app',
data: function () {
return {
myArr: [1,2,3,4,5,6,7,8,9,10]
};
},
filters: {
filtersOdd: function (arr) {
console.log('局部filter的this:');
console.log(this);
return arr.filter(item => !(item % 2));
}
}
})
複製代碼
上面的代碼咱們註冊了一個全局 filter
和一個局部 filter
,打印出來的結果以下: github
能夠看到,都是全局 window
對象。下面就進入 filter
的源碼分析一下爲何沒有綁定當前 Vue
實例。咱們從模板編譯開始看。編譯入口這裏省略,想要了解的童鞋能夠點擊連接查看。直接來到 src\compiler\parser\html-parser.js
的 parseHTML
函數,這裏會遍歷整個模板,filter
屬於文本部分:express
let text, rest, next
if (textEnd >= 0) {
rest = html.slice(textEnd)
while (
!endTag.test(rest) &&
!startTagOpen.test(rest) &&
!comment.test(rest) &&
!conditionalComment.test(rest)
) {
// < in plain text, be forgiving and treat it as text
next = rest.indexOf('<', 1)
if (next < 0) break
textEnd += next
rest = html.slice(textEnd)
}
text = html.substring(0, textEnd)
advance(textEnd)
}
if (textEnd < 0) {
text = html
html = ''
}
if (options.chars && text) {
options.chars(text)
}
複製代碼
判斷 textEnd
是否大於 0,是的話說明當前位置到 textEnd
都是文本內容。而且若是 <
是純文本中的字符,就繼續找到真正的文本結束的位置,而後前進到結束的位置。接着判斷 textEnd
是否小於零,是的話則說明整個 template
解析完畢了,把剩餘的 html
都賦值給了 text
。到這裏咱們就拿到了 :chestnut: 中的文本內容 {{ myArr | filtersOdd | filtersOverFour }}
。接下來執行 chars
回調,這個函數在 src\compiler\parser\index.js
:數組
chars (text: string) {
// 若是沒有父節點
if (!currentParent) {
if (process.env.NODE_ENV !== 'production') {
// 只有template時報錯
if (text === template) {
warnOnce(
'Component template requires a root element, rather than just text.'
)
} else if ((text = text.trim())) {
warnOnce(
`text "${text}" outside root element will be ignored.`
)
}
}
return
}
// IE textarea placeholder bug
/* istanbul ignore if */
if (isIE &&
currentParent.tag === 'textarea' &&
currentParent.attrsMap.placeholder === text
) {
return
}
const children = currentParent.children
text = inPre || text.trim()
? isTextTag(currentParent) ? text : decodeHTMLCached(text)
// only preserve whitespace if its not right after a starting tag
: preserveWhitespace && children.length ? ' ' : ''
if (text) {
let res
if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {
children.push({
type: 2, // 包含表達式的文本
expression: res.expression,
tokens: res.tokens,
text
})
} else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
children.push({
type: 3, // 純文本
text
})
}
}
}
複製代碼
上面代碼先對一些特殊狀況作判斷,好比文本是否直接寫在 template
中,是否是 placeholder
的文本、是否是 script
或者 style
裏面的文本等等。執行完判斷若是不是空字符串且包含表達式,執行 parseText
函數,定義在 src\compiler\parser\text-parser.js
:bash
export function parseText ( text: string, delimiters?: [string, string] ): TextParseResult | void {
const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE
if (!tagRE.test(text)) {
return
}
const tokens = []
const rawTokens = []
let lastIndex = tagRE.lastIndex = 0
let match, index, tokenValue
while ((match = tagRE.exec(text))) {
index = match.index
// push text token
if (index > lastIndex) {
rawTokens.push(tokenValue = text.slice(lastIndex, index))
tokens.push(JSON.stringify(tokenValue))
}
// tag token
const exp = parseFilters(match[1].trim())
tokens.push(`_s(${exp})`)
rawTokens.push({ '@binding': exp })
lastIndex = index + match[0].length
}
if (lastIndex < text.length) {
rawTokens.push(tokenValue = text.slice(lastIndex))
tokens.push(JSON.stringify(tokenValue))
}
return {
expression: tokens.join('+'),
tokens: rawTokens
}
}
複製代碼
defaultTagRE
匹配兩個大括號中間的內容。而後再循環匹配文本,遇到普通文本就 push 到 rawTokens
和 tokens
中,若是是表達式就轉換成 _s(${exp})
push 到 tokens
中,以及轉換成 {@binding:exp}
push 到 rawTokens
中。對於咱們這個:chestnut:,咱們最後獲得的表達式:app
{
expression: [""\n "", "_s(_f("filtersOverFour")(_f("filtersOdd")(myArr)))", ""\n ""],
tokens: ["↵ ", {@binding: "_f("filtersOverFour")(_f("filtersOdd")(myArr))"}, "↵ "]
}
複製代碼
_f
是什麼呢?咱們一塊兒來分析下。上述代碼中,parseFilters
函數就是咱們這節的關鍵,它定義在 src\compiler\parser\filter-parser.js
文件中:ide
/** * 處理text中的filters * @param {String} exp - 字符文本 * @return {String} expression - 處理完filters後的函數 */
export function parseFilters (exp: string): string {
// ...
// 循環文本表達式
for (i = 0; i < exp.length; i++) {
// ...
}
if (expression === undefined) {
expression = exp.slice(0, i).trim()
} else if (lastFilterIndex !== 0) {
pushFilter()
}
/** * 將全部filters處理函數推入到filters數組中 */
function pushFilter () {
(filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim())
lastFilterIndex = i + 1
}
// 遍歷filters全部處理函數,依次包裝。轉換成_f
if (filters) {
for (i = 0; i < filters.length; i++) {
expression = wrapFilter(expression, filters[i])
}
}
// 有兩個filters處理函數生成的表達式 "_f("filtersOverFour")(_f("filtersOdd")(myArr))"
return expression
}
function wrapFilter (exp: string, filter: string): string {
const i = filter.indexOf('(')
if (i < 0) {
// _f: resolveFilter
return `_f("${filter}")(${exp})`
} else {
const name = filter.slice(0, i)
const args = filter.slice(i + 1)
return `_f("${name}")(${exp}${args !== ')' ? ',' + args : args}`
}
}
複製代碼
按照:chestnut:,parseFilters
的做用就是把整個文本表達式轉化成 _f("filtersOverFour")(_f("filtersOdd")(myArr))
。_f 定義在 src\core\instance\render-helpers\index.js
:函數
export function installRenderHelpers (target: any) {
// ...
target._f = resolveFilter
// ...
}
複製代碼
resolveFilter
定義在 src\core\instance\render-helpers\resolve-filter.js
:源碼分析
/** * 獲取filter對象中對應id的函數 * @param {String} id - 函數名 * @returns {Function} - 函數名是id的函數 */
export function resolveFilter (id: string): Function {
return resolveAsset(this.$options, 'filters', id, true) || identity
}
複製代碼
截圖是 this.$options
對象,能夠看到:全局 filter
是掛在實例 filters
屬性原型中的。
生成執行代碼階段就不詳細分析了,最後生成的 render
函數代碼:
with(this){return _c('div',{attrs:{"id":"app"}},[_v("\n "+_s(_f("filtersOverFour")(_f("filtersOdd")(myArr)))+"\n ")])}
複製代碼
最後在調用 vm._render
函數時會執行_f
函數。至此,filter
的流程就走完了。下面經過一個簡單的:chestnut:來還原一下上面的場景:
// 至關於 render 函數
var withFn = (function() {
with (this) {
console.log(this);
b();
}
})
// 至關於_f函數
function b () {
console.log(this);
}
// 至關於 vm._renderProxy
var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}!`);
}
});
withFn.call(obj);
// 輸出結果:
// Proxy {}
// Window {}
複製代碼
這就回答了 filter
函數 this
爲何指向的是 Window
了!
歡迎糾正錯誤!更多內容請 前往博客!!!