【Vue2.0ソースコード学習】フィルター

1. 使用感レビュー

1 はじめに

フィルターは日常の開発において非常に一般的な機能であると考えられ、テンプレート内のテキストの書式設定によく使用されます。フィルターは個別に使用することも、複数を連続して使用することもでき、パラメーターを渡して使用することもできます。では、Vueフィルターはどのように機能するのでしょうか? その内部原理は何ですか? Vueそして、私たちが作成したフィルターを識別するにはどうすればよいでしょうか? 次に、ソース コードの観点からフィルターの内部動作原理を調査し、そのワークフローを分析して、その謎を明らかにします。

2. 使用感レビュー

フィルターの内部原理を紹介する前に、公式ドキュメントに従ってフィルターの使用法を確認してみましょう。

2.1 使用方法

フィルターは、二重中括弧補間と v-bind 式の2 つの方法で使用できます(後者は 2.1.0 以降でサポートされています)。フィルタは、「|」記号で示される JavaScript 式の最後に追加する必要があります。

<!-- 在双花括号中 -->
{
   
   { message | capitalize }}

<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>

2.2 フィルタの定義

コンポーネントのオプションでローカル フィルターを定義できます。

filters: {
    
    
  capitalize: function (value) {
    
    
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
  }
}

インスタンスを作成する前に、Vueグローバル API を使用してVue.filterグローバル フィルターを定義することもできます。

Vue.filter('capitalize', function (value) {
    
    
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

グローバルフィルタとローカルフィルタが同じ名前の場合、ローカルフィルタが採用されます。

2.3 インラインフィルター

フィルター関数は、常に式の値 (前の操作チェーンの結果) を最初の引数として受け取ります。上記の例では、capitalizeフィルター関数はmessage最初の引数として の値を受け取ります。

フィルターは連鎖させることができます。

{
    
    {
    
     message | filterA | filterB }}

この例では、filterAフィルター関数は 1 つの引数を取るように定義されており、message式の値は引数として関数に渡されます。次に、フィルタ関数の呼び出しに進みます。この関数も 1 つの引数を取るように定義されておりfilterBfilterAの結果をfilterBに渡します。

フィルターは JavaScript 関数であるため、パラメーターを受け取ることができます。

{
    
    {
    
     message | filterA('arg1', arg2) }}

ここでは、filterAは 3 つのパラメーターを受け入れるフィルター関数として定義されています。messageの値が最初のパラメータとして使用され、通常の文字列が'arg1'2 番目のパラメータとして使用され、arg2式の値が 3 番目のパラメータとして使用されます。

3. まとめ

使用法のレビューから、フィルターの使用方法は 2 つあることがわかります。1 つは二重中括弧補間で、もう 1 つは v-bind 式です。どちらの方法で使用されても、その使用形態は です表达式 | 过滤器1 | 过滤器2 | ...

さらに、フィルターを定義するには 2 つの方法があることがわかっています。1 つはコンポーネント オプションで定義する方法、もう 1 つはグローバル API を使用してVue.filterグローバル フィルターを定義する方法です。コンポーネント オプションで定義されたフィルターはローカル フィルターと呼ばれ、現在のコンポーネントでのみ使用できます。これを使用して定義されたフィルターVue.filterはグローバル フィルターと呼ばれ、どのコンポーネントでも使用できます。

さらに、フィルターは個別に使用できるだけでなく、連続して使用できることもわかっています。複数のフィルターを連続して使用する場合、前のフィルターの出力が後のフィルターの入力となり、テキストはさまざまなフィルターを組み合わせて最終的に必要な形式に処理されます。

最後に、公式ドキュメントには、いわゆるフィルターは本質的に関数であるJSため、フィルターを使用するときにパラメーターをフィルターに渡すこともできるとも述べられています。フィルターが受け取る最初のパラメーターは常に式の値、または結果です前のフィルターによって処理され、後続のパラメーターの残りはフィルター内のフィルター ルールで使用できます。

フィルターの使用法を理解した後、その内部原理を詳しく見てみましょう。

2.動作原理

1 はじめに

使用法のレビューの章を通じて、フィルターは 2 つの方法、つまり二重中括弧補間と v-bind 式で使用できることがわかりました。ただし、どちらの方法で使用する場合でも、フィルターはテンプレートに記述されます。テンプレートに記述しているので、それをコンパイルして描画関数文字列にコンパイルし、マウント時に描画関数が実行されることでフィルターが有効になります。例えば:

次のフィルターがある場合:

{
    
    {
    
     message | capitalize }}

filters: {
    
    
    capitalize: function (value) {
    
    
        if (!value) return ''
        value = value.toString()
        return value.charAt(0).toUpperCase() + value.slice(1)
    }
}

次に、レンダー関数文字列にコンパイルされると、次のようになります。

_f("capitalize")(message)

_fこのような関数を初めて見た場合でも、パニックにならないでください。これは、テンプレート コンパイルの章でコード生成ステージを紹介したときに見た関数と同じです。_cこれは、_e関数に対応し、関数_fは関数に対応しresolveFilterます。関数呼び出し文字列は、テンプレート コンパイルを通じて生成されます_f。レンダリング関数が実行されると、_f関数が実行されてフィルターが有効になります。

つまり、実際にフィルタを機能させるのは関数_f、つまりresolveFilter関数なので、resolveFilter次に関数の内部原理を分析してみましょう。

2.resolveFilter関数の解析

resolveFilter関数の定義はsrc/core/instance/render-helpers.js、次のようにソース コードにあります。

import {
    
     identity, resolveAsset } from 'core/util/index'

export function resolveFilter (id) {
    
    
  return resolveAsset(this.$options, 'filters', id, true) || identity
}

resolveFilter関数内には、関数を呼び出して戻り値を取得するためのコードが 1 行しかないことがわかりますresolveAsset。戻り値が存在しない場合は を返しますが、identityのようにパラメータと同じ値を返します。 :identity

/**
 * Return same value
 */
export const identity = _ => _

明らかに、私たちがより懸念しているのは関数です。その定義は次のようにresolveAssetソース コードにあります。src/core/util/options.js

export function resolveAsset (options,type,id,warnMissing) {
    
    
  if (typeof id !== 'string') {
    
    
    return
  }
  const assets = options[type]
  // 先从本地注册中查找
  if (hasOwn(assets, id)) return assets[id]
  const camelizedId = camelize(id)
  if (hasOwn(assets, camelizedId)) return assets[camelizedId]
  const PascalCaseId = capitalize(camelizedId)
  if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
  // 再从原型链中查找
  const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
  if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
    
    
    warn(
      'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
      options
    )
  }
  return res
}

この関数を呼び出すと、$options現在のインスタンスの属性であり、現在のフィルタtypeである4 つのパラメータが渡されますfiltersidid

この関数内では、最初に受信パラメータid(つまり、現在のフィルタの名前id) が文字列型であるかどうかを判断し、文字列型でない場合はプログラムを直接終了します。次のように:

if (typeof id !== 'string') {
    
    
    return
}

次に、$options現在のインスタンスのプロパティにあるすべてのフィルターを取得し、それらを変数に割り当てますassets。前の記事で説明したように、フィルターを定義するには 2 つの方法があります。1 つはコンポーネントのオプションで定義する方法、もう 1 つはコンポーネントのオプションで定義する方法です。Vue.filter定義を使用します。$options前の記事で述べたように、コンポーネント内のすべてのオプションは現在のインスタンスのプロパティにマージされ、それらを使用して定義されたフィルターもそのプロパティVue.filterに追加されるため、フィルターがデバイスでどのように定義されているかに関係なく、誰もがそのプロパティからそれを取得できます。次のように:$optionsfilters$optionsfilters

const assets = options[type]

すべてのフィルターを取得したら、id次のようにフィルターに従って対応するフィルター関数を抽出するだけです。

// 先从本地注册中查找
if (hasOwn(assets, id)) return assets[id]
const camelizedId = camelize(id)
if (hasOwn(assets, camelizedId)) return assets[camelizedId]
const PascalCaseId = capitalize(camelizedId)
if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
// 再从原型链中查找
const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
    
    
    warn(
        'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
        options
    )
}
return res

上記のコードでは、フィルタid検索に従って、フィルタはまずローカル登録から検索し、hasOwn関数によってそれ自体に存在するかどうかを確認しassets、存在する場合は直接返し、存在しない場合はフィルタをidcamelに変換します。大文字と小文字を区別して再検索します。存在する場合は直接返します。存在しない場合は、フィルターをid最初の大文字に変換して再検索します。存在する場合は直接返します。存在しない場合は、次から検索します。プロトタイプ チェーンが存在する場合は直接返し、まだ存在する場合はプロトタイプ チェーンが存在しない場合は、非運用環境では警告がスローされます。

上記はresolveFilter関数のロジックのすべてです。resolveFilter実際には、関数がフィルターに従って対応するユーザー定義のフィルター関数を取得して返していることがわかりますid。ユーザー定義のフィルター関数を取得した後、関数を呼び出してパラメーターを渡すと、それが有効になります。以下に示すように:
ここに画像の説明を挿入

3. シリーズフィルターの原理

単一のフィルターの動作原理を上で分析しました。原理は、直列に使用される複数のフィルターでも同じです。最初にフィルターに従ってid対応するフィルター関数を取得し、次に呼び出すパラメーターを渡す方が良いです。唯一の違いは、重要なのは、直列の複数のフィルターの場合、パラメーターを渡すためにフィルター関数を呼び出すとき、後のフィルターの入力パラメーターは前のフィルターの出力結果であるということです。例えば:

次のフィルターがある場合:

{
    
    {
    
     message | filterA | filterB }}

filters: {
    
    
    filterA: function (value) {
    
    
        // ...
    },
    filterB: function (value) {
    
    
        // ...
    },
}

次に、レンダー関数文字列にコンパイルされると、次のようになります。

ここに画像の説明を挿入

ご覧のとおり、フィルターの実行結果はfilterAパラメーターとしてフィルターに渡されますfilterB

4. フィルターはパラメータを受け取ります

前の記事で述べたように、フィルターは本質的に関数ですJS。関数であるため、パラメーターを確実に受け取ることができます。唯一注意すべき点は、フィルターの最初のパラメーターは常に式の値であるか、または前のフィルタ処理の結果、残りのパラメータはフィルタ内のフィルタリング ルールで使用できます。例えば:

次のフィルターがある場合:

{
    
    {
    
     message | filterA | filterB(arg) }}

filters: {
    
    
    filterA: function (value) {
    
    
        // ...
    },
    filterB: function (value,arg) {
    
    
        return value + arg
    },
}

次に、レンダー関数文字列にコンパイルされると、次のようになります。

ここに画像の説明を挿入

フィルターが残りのパラメーターを受け取ると、そのパラメーターが 2 番目以降のパラメーターに渡されることがわかります。

5. まとめ

この記事では、フィルターの内部動作原理を紹介します。これは、ユーザーがテンプレートに記述したフィルターをテンプレートを通じてコン​​パイルし、それを関数_f呼び出し文字列にコンパイルし、レンダリング関数の実行時にその関数を実行すること_fです。フィルタが有効になります。

いわゆる_f関数は実際にはresolveFilter関数のエイリアスであり、関数内では、対応するフィルター関数がresolveFilterfilter に従ってid現在のインスタンス$optionsのプロパティから取得されfilters、取得されたフィルター関数は、後でレンダリング関数が実行されるときに実行されます。 。

フィルターの動作原理は理解できたので、Vueユーザーが作成したフィルターを識別し、テンプレートのコンパイル時にフィルター内のコンテンツを解析するにはどうすればよいでしょうか? Vue次の記事では、フィルターを解析する方法を紹介します。

3、分析フィルター

1 はじめに

動作原理では、ユーザーがフィルターをどのように使用しても、結局パーサーはテンプレートに記述されていると述べました。テンプレートに含まれているため、必ず解析され、コンパイルされます。ユーザーが記述したテンプレートを解析することで、次に、message | filterA | filterBユーザーが作成したフィルターのどの部分が処理対象の式であり、どの部分がフィルターidとそのパラメーターであるかを解析します。

HTMLテンプレートのコンパイルの章の解析フェーズで述べたことを思い出してください。ユーザーが作成したテンプレートは、 parser parseHTML、 text parser parseText、および filter parserという 3 つのパーサーによって解析されますparseFiltersその中で、パーサーがメインラインです。パーサー関数HTMLを使用してテンプレート内のタグを解析するプロセスで、テキスト情報が見つかった場合は、テキスト パーサー関数がテキスト解析のために呼び出されます。テキストにフィルターが含まれている場合は、テキスト パーサー関数が呼び出されます。フィルターが呼び出されます。 パーサー関数が解析を行います。前回の記事では、パーサーとテキスト パーサーの内部原理のみを分析し、フィルター パーサーの分析を行っていないため、この記事ではフィルター パーサーの内部原理を分析します。HTMLparseHTMLHTMLparseTextparseFiltersHTMLparseHTMLparseTextparseFilters

2. フィルターを解析する場所

フィルターを使用するには 2 つの方法があることを繰り返し強調してきました。つまり、次のように二重中括弧補間と v-bind 式です。

<!-- 在双花括号中 -->
{
    
    {
    
     message | capitalize }}

<!--`v-bind`-->
<div v-bind:id="rawId | formatId"></div>

2 つの異なる使用方法の唯一の違いは、フィルターが異なる場所に記述されることです。フィルターを記述できる場所が 2 つあるため、解析時には両方の異なる場所で解析する必要があります。

  • v-bind 式で記述

    v-bind 式内のフィルターはタグ属性に属するため、タグ属性を処理するときにそこに記述されたフィルターを解析する必要があります。HTMLパーサー関数parseHTMLでタグ属性の処理を担当する関数は であることがわかっているprocessAttrsため、次のように、関数内でprocessAttrsフィルター パーサーparseFilters関数が呼び出され、そこに記述されたフィルターが解析されます。

    function processAttrs (el) {
          
          
        // 省略无关代码...
        if (bindRE.test(name)) {
          
           // v-bind
            // 省略无关代码...
            value = parseFilters(value)
            // 省略无关代码...
        }
        // 省略无关代码...
    }
    
  • 二重中括弧で書かれています

    二重中括弧内のフィルターはタグ テキストに属するため、タグ テキストを処理するときに、そこに記述されたフィルターを解析する必要があります。HTMLパーサーparseHTML関数では、テキスト情報に遭遇するとparseHTML関数のcharsフック関数が呼び出され、charsフック関数内でテキストパーサー関数が呼び出されてparseTextテキストを解析し、そこに書かれたフィルターがテキスト内に存在することがわかっていますので、次のように、テキスト パーサー関数内でフィルターparseTextパーサー関数が呼び出されparseFilters、そこに記述されたフィルターが解析されます。

    export function parseText (text,delimiters){
          
          
        // 省略无关代码...
        const exp = parseFilters(match[1].trim())
        // 省略无关代码...
    }
    

フィルターがどこで解析されるかがわかったので、フィルター パーサーparseFilters関数を分析して、フィルターが内部でどのように解析されるかを確認しましょう。

3. ParseFilters 関数の分析

parseFilters関数の定義はソース コードsrc/complier/parser/filter-parser.jsファイルにあり、そのコードは次のとおりです。

export function parseFilters (exp) {
    
    
  let inSingle = false                     // exp是否在 '' 中
  let inDouble = false                     // exp是否在 "" 中
  let inTemplateString = false             // exp是否在 `` 中
  let inRegex = false                      // exp是否在 \\ 中
  let curly = 0                            // 在exp中发现一个 { 则curly加1,发现一个 } 则curly减1,直到culy为0 说明 { ... }闭合
  let square = 0                           // 在exp中发现一个 [ 则curly加1,发现一个 ] 则curly减1,直到culy为0 说明 [ ... ]闭合
  let paren = 0                            // 在exp中发现一个 ( 则curly加1,发现一个 ) 则curly减1,直到culy为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
    ) {
    
    
      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()
  }

  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
}

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}`
  }
}

この関数の機能は、'message | capitalize'このような受信フィルター文字列を に変換し_f("capitalize")(message)、その内部ロジックを分析することです。

関数内では、次のようにいくつかの変数が最初に定義されます。

let inSingle = false
let inDouble = false
let inTemplateString = false
let inRegex = false
let curly = 0
let square = 0
let paren = 0
let lastFilterIndex = 0
  • inSingle: フラグ exp が '...' にあるかどうか。
  • inDouble: フラグ exp が "..." にあるかどうか。
  • inTemplateString: フラグ exp が...入っているかどうか。
  • inRegex: フラグ exp が \...\ にあるかどうか;
  • curly = 0 : exp で { が見つかった場合は、curly に 1 を加えます。 } が見つかった場合、curly は 0 になるまで 1 ずつ減らされ、{ ... } が閉じていることを示します。
  • square = 0: exp で 1 が見つかった場合、[...] が閉じていることを示す culy が 0 になるまで、curly は 1 ずつ減分されます。
  • paren = 0: exp で 1 を見つけて (次に、curly に 1 を加えて 1 を見つけます)、その後、culy が 0 (…) が閉じていることを示すまで、curly を 1 ずつ減分します。
  • lastFilterIndex = 0: カーソルを解析し、ループ後に各文字列カーソルに 1 を追加します。

exp次に、各受信文字を先頭からたどって、次のように各文字が特殊文字 ( '"{ }[]()など)\であるかどうかを判断して、文字列のどの部分が式であり、どの部分がフィルタであるかを判断します。|expid

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
    ) {
    
    
        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()
}

function pushFilter () {
    
    
    (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim())
    lastFilterIndex = i + 1
}

ご覧のとおり、コードは少し長くなりますが、ロジックは非常に単純です。ASCII読みやすくするために、コードと上記のコードに含まれる文字との対応を次のように示します。

0x22 ----- "
0x27 ----- '
0x28 ----- (
0x29 ----- )
0x2f ----- /
0x5C ----- \
0x5B ----- [
0x5D ----- ]
0x60 ----- `
0x7C ----- |
0x7B ----- {
0x7D ----- }

上記のコードのロジックは、exp文字列の各文字を前から後ろに 1 つずつ照合し、 、 、'{ "} [ ] ( ) ` などの特殊文字と照合することです ,,,,,,|

一致した場合'"字符,说明当前字符在字符串中,那么直到匹配到下一个同样的字符才结束,同时, 匹配 () , {} ,[] 这些需要两边相等闭合, 那么匹配到的| 才被认为是过滤器中的|`。

フィルタ内の文字が一致した場合||その文字の前の文字列を処理対象の式とみなし、 に格納され、expression以降も一致が継続されます|expressionこの時点で値がある場合、それは後ろに 2 番目のフィルターがあることを意味し、|2 つの文字の間の文字列が最初のフィルターとなりidpushFilter最初のフィルターをfilters配列に追加するために関数が呼び出されます。例えば:

次のフィルター文字列があるとします。

message | filter1 | filter2(arg)

その照合プロセスを次の図に示します。

ここに画像の説明を挿入

上記の例のすべてのフィルター文字列を照合すると、次の結果が得られます。

expression = message
filters = ['filter1','filter2(arg)']

次に、次のように、取得した配列を走査し、filters配列の各要素を関数にexpression渡して、最終的な関数呼び出し文字列を生成します。wrapFilter_f

if (filters) {
    
    
    for (i = 0; i < filters.length; i++) {
    
    
        expression = wrapFilter(expression, filters[i])
    }
}

function wrapFilter (exp, filter) {
    
    
  const i = filter.indexOf('(')
  if (i < 0) {
    
    
    return `_f("${
      
      filter}")(${
      
      exp})`
  } else {
    
    
    const name = filter.slice(0, i)
    const args = filter.slice(i + 1)
    return `_f("${
      
      name}")(${
      
      exp}${
      
      args !== ')' ? ',' + args : args}`
  }
}

wrapFilterこの関数では、まず、解析によって得られた各フィルターに何かがあるかどうかを確認して(、フィルターがパラメーターを受け取ったかどうかを判断し、そうでない場合は(、フィルターがパラメーターを受け取っていないことを意味し、その後、直接構築することがわかります。_f関数呼び出し 次のように、文字列が_f("filter1")(message)割り当てられ、 に返されますexpression

const i = filter.indexOf('(')
if (i < 0) {
    
    
    return `_f("${
      
      filter}")(${
      
      exp})`
}

次に、新しいフィルターexperssionfilters配列内の次のフィルターを使用して関数を呼び出しますwrapFilter。次のフィルターにパラメーターがある場合は、最初にフィルターを取り出し、id次にフィルターが持つパラメーターを取り出して、_f2 番目のフィルターの関数呼び出し文字列を生成します。つまり_f("filter2")(_f("filter1")(message),arg)、次のようになります。

const name = filter.slice(0, i)
const args = filter.slice(i + 1)
return `_f("${
      
      name}")(${
      
      exp}${
      
      args !== ')' ? ',' + args : args}`

これにより、最終的に、_fユーザーが作成したフィルターの関数呼び出し文字列が生成されます。

4. まとめ

この記事では、Vueユーザーが作成したフィルターを解析する方法について説明します。

まず、2 つの異なる書き方のフィルターが異なる場所で解析されることを紹介しましたが、解析原理は同じであり、解析にはフィルター パーサー関数が呼び出されますparseFilters

次に、parseFilters関数の内部ロジックを分析しました。この関数は、次のような'message | capitalize'フィルター文字列を受け取り、それを_f("capitalize")(message)出力に変換します。関数内ではparseFilters、受信フィルタ文字列の各文字が調べられ、各文字が特殊文字であるかどうかに応じて異なる処理が実行されます。最後に、保留中の処理が受信フィルタ文字列式とすべてのフィルタ配列から解析されexpressionますfilters

最後に、関数を呼び出すことによって、解析されたexpression合計配列が関数呼び出し文字列に構築されます。filterswrapFilter_f

おすすめ

転載: blog.csdn.net/weixin_46862327/article/details/131526913