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 つの引数を取るように定義されておりfilterB
、filterA
の結果を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 つのパラメータが渡されます。filters
id
id
この関数内では、最初に受信パラメータid
(つまり、現在のフィルタの名前id
) が文字列型であるかどうかを判断し、文字列型でない場合はプログラムを直接終了します。次のように:
if (typeof id !== 'string') {
return
}
次に、$options
現在のインスタンスのプロパティにあるすべてのフィルターを取得し、それらを変数に割り当てますassets
。前の記事で説明したように、フィルターを定義するには 2 つの方法があります。1 つはコンポーネントのオプションで定義する方法、もう 1 つはコンポーネントのオプションで定義する方法です。Vue.filter
定義を使用します。$options
前の記事で述べたように、コンポーネント内のすべてのオプションは現在のインスタンスのプロパティにマージされ、それらを使用して定義されたフィルターもそのプロパティVue.filter
に追加されるため、フィルターがデバイスでどのように定義されているかに関係なく、誰もがそのプロパティからそれを取得できます。次のように:$options
filters
$options
filters
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
、存在する場合は直接返し、存在しない場合はフィルタをid
camelに変換します。大文字と小文字を区別して再検索します。存在する場合は直接返します。存在しない場合は、フィルターを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
関数のエイリアスであり、関数内では、対応するフィルター関数がresolveFilter
filter に従ってid
現在のインスタンス$options
のプロパティから取得されfilters
、取得されたフィルター関数は、後でレンダリング関数が実行されるときに実行されます。 。
フィルターの動作原理は理解できたので、Vue
ユーザーが作成したフィルターを識別し、テンプレートのコンパイル時にフィルター内のコンテンツを解析するにはどうすればよいでしょうか? Vue
次の記事では、フィルターを解析する方法を紹介します。
3、分析フィルター
1 はじめに
動作原理では、ユーザーがフィルターをどのように使用しても、結局パーサーはテンプレートに記述されていると述べました。テンプレートに含まれているため、必ず解析され、コンパイルされます。ユーザーが記述したテンプレートを解析することで、次に、message | filterA | filterB
ユーザーが作成したフィルターのどの部分が処理対象の式であり、どの部分がフィルターid
とそのパラメーターであるかを解析します。
HTML
テンプレートのコンパイルの章の解析フェーズで述べたことを思い出してください。ユーザーが作成したテンプレートは、 parser parseHTML
、 text parser parseText
、および filter parserという 3 つのパーサーによって解析されますparseFilters
。その中で、パーサーがメインラインです。パーサー関数HTML
を使用してテンプレート内のタグを解析するプロセスで、テキスト情報が見つかった場合は、テキスト パーサー関数がテキスト解析のために呼び出されます。テキストにフィルターが含まれている場合は、テキスト パーサー関数が呼び出されます。フィルターが呼び出されます。 パーサー関数が解析を行います。前回の記事では、パーサーとテキスト パーサーの内部原理のみを分析し、フィルター パーサーの分析を行っていないため、この記事ではフィルター パーサーの内部原理を分析します。HTML
parseHTML
HTML
parseText
parseFilters
HTML
parseHTML
parseText
parseFilters
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
次に、各受信文字を先頭からたどって、次のように各文字が特殊文字 ( '
、"
、{
、}
、[
、]
、(
、)
、など)\
であるかどうかを判断して、文字列のどの部分が式であり、どの部分がフィルタであるかを判断します。|
exp
id
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 つの文字の間の文字列が最初のフィルターとなりid
、pushFilter
最初のフィルターを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})`
}
次に、新しいフィルターexperssion
とfilters
配列内の次のフィルターを使用して関数を呼び出しますwrapFilter
。次のフィルターにパラメーターがある場合は、最初にフィルターを取り出し、id
次にフィルターが持つパラメーターを取り出して、_f
2 番目のフィルターの関数呼び出し文字列を生成します。つまり_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
合計配列が関数呼び出し文字列に構築されます。filters
wrapFilter
_f