なぜfilterのthisがVueではないのですか?

17601 ワード

最近の仕事で問題になったのは、filterの関数がVue.prototypeにバインドされた関数を使用できないことです.周知のように、createdmountedmethodsのうち、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.jsparseHTML関数にアクセスします.ここではテンプレート全体を巡回します.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)
}

判断textEnd0より大きいか、そうであれば現在位置を示す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
  }
}
defaultTagRE2つの括弧の間の内容を一致させる.そしてマッチングテキストを再循環し、通常テキストに遭遇したらpushからrawTokenstokensと、式であれば_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