なぜfilterのthisがVueではないのですか?
17601 ワード
最近の仕事で問題になったのは、filterの関数が
直接例:
上記のコードは、グローバル
全局
判断
上のコードは、まず、テキストが
按照:chestnut:,
スクリーンショットは
実行コードを生成する段階では詳細は分析されず、最後に生成された
最後に
これで答えました
間違いの修正を歓迎します.詳細はブログへ!!
転載先:https://juejin.im/post/5cbd2623f265da03ba0e2aab
Vue.prototype
にバインドされた関数を使用できないことです.周知のように、created
・mounted
・methods
のうち、this
バインドされているのはいずれも現在Vue
インスタンスである.あいにくfilter
関数this
指すのはWindow
?直接例:
<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
を登録しています.印刷結果は以下の通りです.全局
window
対象であることがわかる.次に、filter
のソースコードに入り、現在Vue
インスタンスがバインドされていない理由を分析します.テンプレートのコンパイルから見ます.コンパイル入口はここでは省略しますが、知りたい子供靴はリンクをクリックして見ることができます.直接src\compiler\parser\html-parser.js
のparseHTML
関数にアクセスします.ここではテンプレート全体を巡回します.filter
テキスト部分に属します.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
: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
2つの括弧の間の内容を一致させる.そしてマッチングテキストを再循環し、通常テキストに遭遇したらpushからrawTokens
とtokens
と、式であれば_s(${exp})
pushからtokens
と、{@binding:exp}
pushからrawTokens
に変換する.これ:chestnut:最後に得られた式:{
expression: [""
"", "_s(_f("filtersOverFour")(_f("filtersOdd")(myArr)))", ""
""],
tokens: ["↵ ", {@binding: "_f("filtersOverFour")(_f("filtersOdd")(myArr))"}, "↵ "]
}
_f
何ですか?一緒に分析しましょう.上記のコードでは、parseFilters
関数がこのセクションのキーです.src\compiler\parser\filter-parser.js
ファイルに定義されています./**
* 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("
"+_s(_f("filtersOverFour")(_f("filtersOdd")(myArr)))+"
")])}
最後に
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
です!間違いの修正を歓迎します.詳細はブログへ!!
転載先:https://juejin.im/post/5cbd2623f265da03ba0e2aab