Vue.js でのテンプレートのコンパイルの問題を解決する方法
この記事では主に Vue.js のテンプレート コンパイルの問題について紹介します。この記事を読んだ後、Vue.js のテンプレート コンパイルの問題が明確に解決されることを願っています。
前に書きました
なぜなら、私は Vue.js に非常に興味があり、私が普段取り組んでいる技術スタックも Vue.js であるため、ここ数か月間、Vue.js のソース コードを勉強するのに時間を費やしました。をまとめて出力しました。
記事の元のアドレス: https://github.com/answershuto/learnVue。
学習プロセス中に、Vue https://github.com/answershuto/learnVue/tree/master/vue-src に中国語のコメントが追加されました。Vue のソース コードを学習したい他の友人に役立つことを願っています。
理解にギャップがあるかもしれませんが、一緒に学び、進歩するために問題を提起し、指摘することを歓迎します。
$mount
まずマウント コードを見てください
/*把原本不带编译的$mount方法保存下来,在最后会调用。*/ const mount = Vue.prototype.$mount /*挂载组件,带模板编译*/ Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) /* istanbul ignore if */ if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== 'production' && warn( `Do not mount Vue to <html> or <body> - mount to normal elements instead.` ) return this } const options = this.$options // resolve template/el and convert to render function /*处理模板templete,编译成render函数,render不存在的时候才会编译template,否则优先使用render*/ if (!options.render) { let template = options.template /*template存在的时候取template,不存在的时候取el的outerHTML*/ if (template) { /*当template是字符串的时候*/ if (typeof template === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !template) { warn( `Template element not found or is empty: ${options.template}`, this ) } } } else if (template.nodeType) { /*当template为DOM节点的时候*/ template = template.innerHTML } else { /*报错*/ if (process.env.NODE_ENV !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { /*获取element的outerHTML*/ template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } /*将template编译成render函数,这里会有render以及staticRenderFns两个返回,这是vue的编译时优化,static静态不需要在VNode更新时进行patch,优化性能*/ const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, delimiters: options.delimiters }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`${this._name} compile`, 'compile', 'compile end') } } } /*Github:https://github.com/answershuto*/ /*调用const mount = Vue.prototype.$mount保存下来的不带编译的mount*/ return mount.call(this, el, hydrating) }
マウント コードを通して、マウント プロセス中に、レンダー関数が存在しない場合 (レンダー関数が存在する場合は最初にレンダーが使用される)、テンプレートは、render と staticRenderFns を取得するために、ToFunctions にコンパイルされます。たとえば、テンプレートが手書きコンポーネントに追加された場合、実行時にコンパイルされます。レンダリング関数は、更新中にページのレンダリングとパッチ適用のために実行された後、VNode ノードを返します。次に、テンプレートがどのようにコンパイルされるかを見てみましょう。
いくつかの基本
まず、テンプレートは AST 構文ツリーにコンパイルされます。AST とは何ですか?
コンピューター サイエンスにおいて、抽象構文ツリー (AST と略称)、または構文ツリー (構文ツリー) は、ソース コードの抽象構文構造をツリー状に表現したもので、ここでは特にプログラミング言語のソース コードを指します。
AST は、generate を通じて render 関数を取得します。render の戻り値は VNode です。VNode の具体的な定義は次のとおりです:
export default class VNode { tag: string | void; data: VNodeData | void; children: ?Array<VNode>; text: string | void; elm: Node | void; ns: string | void; context: Component | void; // rendered in this component's scope functionalContext: Component | void; // only for functional component root nodes key: string | number | void; componentOptions: VNodeComponentOptions | void; componentInstance: Component | void; // component instance parent: VNode | void; // component placeholder node raw: boolean; // contains raw HTML? (server only) isStatic: boolean; // hoisted static node isRootInsert: boolean; // necessary for enter transition check isComment: boolean; // empty comment placeholder? isCloned: boolean; // is a cloned node? isOnce: boolean; // is a v-once node? /*Github:https://github.com/answershuto*/ constructor ( tag?: string, data?: VNodeData, children?: ?Array<VNode>, text?: string, elm?: Node, context?: Component, componentOptions?: VNodeComponentOptions ) { /*当前节点的标签名*/ this.tag = tag /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/ this.data = data /*当前节点的子节点,是一个数组*/ this.children = children /*当前节点的文本*/ this.text = text /*当前虚拟节点对应的真实dom节点*/ this.elm = elm /*当前节点的名字空间*/ this.ns = undefined /*编译作用域*/ this.context = context /*函数化组件作用域*/ this.functionalContext = undefined /*节点的key属性,被当作节点的标志,用以优化*/ this.key = data && data.key /*组件的option选项*/ this.componentOptions = componentOptions /*当前节点对应的组件的实例*/ this.componentInstance = undefined /*当前节点的父节点*/ this.parent = undefined /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/ this.raw = false /*静态节点标志*/ this.isStatic = false /*是否作为跟节点插入*/ this.isRootInsert = true /*是否为注释节点*/ this.isComment = false /*是否为克隆节点*/ this.isCloned = false /*是否有v-once指令*/ this.isOnce = false } // DEPRECATED: alias for componentInstance for backwards compat. /* istanbul ignore next */ get child (): Component | void { return this.componentInstance } }
VNode の詳細については、VNode ノードを参照してください。 。
createCompiler
createCompilerはコンパイラの作成に使用され、戻り値はcompileとcompileToFunctionsです。コンパイルは、受信したテンプレートを対応する AST ツリー、レンダー関数、および staticRenderFns 関数に変換するコンパイラーです。 compileToFunctions はキャッシュされたコンパイラであり、staticRenderFns およびレンダリング関数は Function オブジェクトに変換されます。
異なるプラットフォームには異なるオプションがあるため、createCompiler はプラットフォームに応じて BaseOptions を渡し、コンパイル自体によって渡されたオプションとマージされて、最終的な FinalOptions を取得します。
compileToFunctions
まず、compileToFunctionsのコードを投稿しましょう。
/*带缓存的编译器,同时staticRenderFns以及render函数会被转换成Funtion对象*/ function compileToFunctions ( template: string, options?: CompilerOptions, vm?: Component ): CompiledFunctionResult { options = options || {} /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production') { // detect possible CSP restriction try { new Function('return 1') } catch (e) { if (e.toString().match(/unsafe-eval|CSP/)) { warn( 'It seems you are using the standalone build of Vue.js in an ' + 'environment with Content Security Policy that prohibits unsafe-eval. ' + 'The template compiler cannot work in this environment. Consider ' + 'relaxing the policy to allow unsafe-eval or pre-compiling your ' + 'templates into render functions.' ) } } } /*Github:https://github.com/answershuto*/ // check cache /*有缓存的时候直接取出缓存中的结果即可*/ const key = options.delimiters ? String(options.delimiters) + template : template if (functionCompileCache[key]) { return functionCompileCache[key] } // compile /*编译*/ const compiled = compile(template, options) // check compilation errors/tips if (process.env.NODE_ENV !== 'production') { if (compiled.errors && compiled.errors.length) { warn( `Error compiling template:\n\n${template}\n\n` + compiled.errors.map(e => `- ${e}`).join('\n') + '\n', vm ) } if (compiled.tips && compiled.tips.length) { compiled.tips.forEach(msg => tip(msg, vm)) } } // turn code into functions const res = {} const fnGenErrors = [] /*将render转换成Funtion对象*/ res.render = makeFunction(compiled.render, fnGenErrors) /*将staticRenderFns全部转化成Funtion对象 */ const l = compiled.staticRenderFns.length res.staticRenderFns = new Array(l) for (let i = 0; i < l; i++) { res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i], fnGenErrors) } // check function generation errors. // this should only happen if there is a bug in the compiler itself. // mostly for codegen development use /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production') { if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) { warn( `Failed to generate render function:\n\n` + fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n$[code]\n`).join('\n'), vm ) } } /*存放在缓存中,以免每次都重新编译*/ return (functionCompileCache[key] = res) }
クロージャ内に、キャッシュとして functionCompileCache オブジェクトがあることがわかります。
/*作为缓存,防止每次都重新编译*/ const functionCompileCache: { [key: string]: CompiledFunctionResult; } = Object.create(null)
compileToFunctions を入力すると、まずキャッシュにコンパイル結果があるかどうかを確認し、結果がある場合はキャッシュから直接読み取ります。これにより、同じテンプレートを毎回繰り返しコンパイルする必要がなくなります。
// check cache /*有缓存的时候直接取出缓存中的结果即可*/ const key = options.delimiters ? String(options.delimiters) + template : template if (functionCompileCache[key]) { return functionCompileCache[key] }
compileToFunctionsの最後に、コンパイル結果がキャッシュされます
/*存放在缓存中,以免每次都重新编译*/ return (functionCompileCache[key] = res)
compile
/*编译,将模板template编译成AST树、render函数以及staticRenderFns函数*/ function compile ( template: string, options?: CompilerOptions ): CompiledResult { const finalOptions = Object.create(baseOptions) const errors = [] const tips = [] finalOptions.warn = (msg, tip) => { (tip ? tips : errors).push(msg) } /*做下面这些merge的目的因为不同平台可以提供自己本身平台的一个baseOptions,内部封装了平台自己的实现,然后把共同的部分抽离开来放在这层compiler中,所以在这里需要merge一下*/ if (options) { // merge custom modules /*合并modules*/ if (options.modules) { finalOptions.modules = (baseOptions.modules || []).concat(options.modules) } // merge custom directives if (options.directives) { /*合并directives*/ finalOptions.directives = extend( Object.create(baseOptions.directives), options.directives ) } // copy other options for (const key in options) { /*合并其余的options,modules与directives已经在上面做了特殊处理了*/ if (key !== 'modules' && key !== 'directives') { finalOptions[key] = options[key] } } } /*基础模板编译,得到编译结果*/ const compiled = baseCompile(template, finalOptions) if (process.env.NODE_ENV !== 'production') { errors.push.apply(errors, detectErrors(compiled.ast)) } compiled.errors = errors compiled.tips = tips return compiled }
compileは主に2つのことを行います。1つはオプションをマージすることです(前述のように、プラットフォーム独自のオプションを受信オプションとマージします)。もう 1 つは、テンプレートをコンパイルする BaseCompile です。
baseCompile を見てみましょう
baseCompile
function baseCompile ( template: string, options: CompilerOptions ): CompiledResult { /*parse解析得到ast树*/ const ast = parse(template.trim(), options) /* 将AST树进行优化 优化的目标:生成模板AST树,检测不需要进行DOM改变的静态子树。 一旦检测到这些静态树,我们就能做以下这些事情: 1.把它们变成常数,这样我们就再也不需要每次重新渲染时创建新的节点了。 2.在patch的过程中直接跳过。 */ optimize(ast, options) /*根据ast树生成所需的code(内部包含render与staticRenderFns)*/ const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } }
baseCompile は、まずテンプレートを解析して AST 構文ツリーを取得し、次に optimize を通じて最適化を行い、最後にgenerate を通じて render と staticRenderFns を取得します。
parse
parse ソース コードは、https://github.com/answershuto/learnVue/blob/master/vue-src/compiler/parser/index.js#L53 にあります。
parse は、通常のメソッドを使用してテンプレート内の命令、クラス、スタイル、その他のデータを解析し、AST 構文ツリーを形成します。
optimize
optimize の主な機能は、静的ノードをマークすることです。これは、後でインターフェースを更新する際に、パッチ処理が行われ、差分アルゴリズムが静的ノードを直接スキップします。比較プロセスを削減し、パッチのパフォーマンスを最適化します。
generate
generate は、AST 構文ツリーをレンダリング関数文字列に変換するプロセスであり、結果はレンダリング文字列と staticRenderFns 文字列になります。
この時点で、テンプレート テンプレートは、必要な AST 構文ツリー、レンダー関数文字列、および staticRenderFns 文字列に変換されました。
たとえば
このコードのコンパイル結果を見てみましょう
<p class="main" :class="bindClass"> <p>{{text}}</p> <p>hello world</p> <p v-for="(item, index) in arr"> <p>{{item.name}}</p> <p>{{item.value}}</p> <p>{{index}}</p> <p>---</p> </p> <p v-if="text"> {{text}} </p> <p v-else></p> </p>
変換後、以下に示すようなASTツリーが得られます:
最も外側のpが次のルートであることがわかります。この AST ツリー ノードには、ノードの形状を表す多くのデータがあります。たとえば、static は静的ノードかどうかを示し、staticClass は静的クラス属性 (bind:class ではありません) を示します。 Children はノードの子ノードを表し、children は長さ 4 の配列であり、そのノードの下に 4 つの p 子ノードが含まれていることがわかります。子のノードは親ノードと同様の構造を持ち、階層ごとに AST 構文ツリーを形成します。
ASTから取得したレンダー関数を見てみましょう
with(this){ return _c( 'p', { /*static class*/ staticClass:"main", /*bind class*/ class:bindClass }, [ _c( 'p', [_v(_s(text))]), _c('p',[_v("hello world")]), /*这是一个v-for循环*/ _l( (arr), function(item,index){ return _c( 'p', [_c('p',[_v(_s(item.name))]), _c('p',[_v(_s(item.value))]), _c('p',[_v(_s(index))]), _c('p',[_v("---")])] ) } ), /*这是v-if*/ (text)?_c('p',[_v(_s(text))]):_c('p',[_v("no text")])], 2 ) }
_c, _v, _s, _q
レンダー関数の文字列を見てみると、_c, _v, _s, _qが大量にあることが分かりました。これらの機能は何ですか?
带着问题,我们来看一下core/instance/render。
/*处理v-once的渲染函数*/ Vue.prototype._o = markOnce /*将字符串转化为数字,如果转换失败会返回原字符串*/ Vue.prototype._n = toNumber /*将val转化成字符串*/ Vue.prototype._s = toString /*处理v-for列表渲染*/ Vue.prototype._l = renderList /*处理slot的渲染*/ Vue.prototype._t = renderSlot /*检测两个变量是否相等*/ Vue.prototype._q = looseEqual /*检测arr数组中是否包含与val变量相等的项*/ Vue.prototype._i = looseIndexOf /*处理static树的渲染*/ Vue.prototype._m = renderStatic /*处理filters*/ Vue.prototype._f = resolveFilter /*从config配置中检查eventKeyCode是否存在*/ Vue.prototype._k = checkKeyCodes /*合并v-bind指令到VNode中*/ Vue.prototype._b = bindObjectProps /*创建一个文本节点*/ Vue.prototype._v = createTextVNode /*创建一个空VNode节点*/ Vue.prototype._e = createEmptyVNode /*处理ScopedSlots*/ Vue.prototype._u = resolveScopedSlots /*创建VNode节点*/ vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
通过这些函数,render函数最后会返回一个VNode节点,在_update的时候,经过patch与之前的VNode节点进行比较,得出差异后将这些差异渲染到真实的DOM上。
相关推荐:
以上がVue.js でのテンプレートのコンパイルの問題を解決する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック











WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法 はじめに: 技術の継続的な発展により、音声認識技術は人工知能の分野の重要な部分になりました。 WebSocket と JavaScript をベースとしたオンライン音声認識システムは、低遅延、リアルタイム、クロスプラットフォームという特徴があり、広く使用されるソリューションとなっています。この記事では、WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法を紹介します。

WebSocketとJavaScript:リアルタイム監視システムを実現するためのキーテクノロジー はじめに: インターネット技術の急速な発展に伴い、リアルタイム監視システムは様々な分野で広く利用されています。リアルタイム監視を実現するための重要なテクノロジーの 1 つは、WebSocket と JavaScript の組み合わせです。この記事では、リアルタイム監視システムにおける WebSocket と JavaScript のアプリケーションを紹介し、コード例を示し、その実装原理を詳しく説明します。 1.WebSocketテクノロジー

JavaScript と WebSocket を使用してリアルタイム オンライン注文システムを実装する方法の紹介: インターネットの普及とテクノロジーの進歩に伴い、ますます多くのレストランがオンライン注文サービスを提供し始めています。リアルタイムのオンライン注文システムを実装するには、JavaScript と WebSocket テクノロジを使用できます。 WebSocket は、TCP プロトコルをベースとした全二重通信プロトコルで、クライアントとサーバー間のリアルタイム双方向通信を実現します。リアルタイムオンラインオーダーシステムにおいて、ユーザーが料理を選択して注文するとき

WebSocket と JavaScript を使用してオンライン予約システムを実装する方法 今日のデジタル時代では、ますます多くの企業やサービスがオンライン予約機能を提供する必要があります。効率的かつリアルタイムのオンライン予約システムを実装することが重要です。この記事では、WebSocket と JavaScript を使用してオンライン予約システムを実装する方法と、具体的なコード例を紹介します。 1. WebSocket とは何ですか? WebSocket は、単一の TCP 接続における全二重方式です。

JavaScript と WebSocket: 効率的なリアルタイム天気予報システムの構築 はじめに: 今日、天気予報の精度は日常生活と意思決定にとって非常に重要です。テクノロジーの発展に伴い、リアルタイムで気象データを取得することで、より正確で信頼性の高い天気予報を提供できるようになりました。この記事では、JavaScript と WebSocket テクノロジを使用して効率的なリアルタイム天気予報システムを構築する方法を学びます。この記事では、具体的なコード例を通じて実装プロセスを説明します。私たちは

JavaScript チュートリアル: HTTP ステータス コードを取得する方法、特定のコード例が必要です 序文: Web 開発では、サーバーとのデータ対話が頻繁に発生します。サーバーと通信するとき、多くの場合、返された HTTP ステータス コードを取得して操作が成功したかどうかを判断し、さまざまなステータス コードに基づいて対応する処理を実行する必要があります。この記事では、JavaScript を使用して HTTP ステータス コードを取得する方法を説明し、いくつかの実用的なコード例を示します。 XMLHttpRequestの使用

JavaScript は Web 開発で広く使用されているプログラミング言語であり、WebSocket はリアルタイム通信に使用されるネットワーク プロトコルです。 2 つの強力な機能を組み合わせることで、効率的なリアルタイム画像処理システムを構築できます。この記事では、JavaScript と WebSocket を使用してこのシステムを実装する方法と、具体的なコード例を紹介します。まず、リアルタイム画像処理システムの要件と目標を明確にする必要があります。リアルタイムの画像データを収集できるカメラ デバイスがあるとします。

JavaScript で HTTP ステータス コードを取得する方法の紹介: フロントエンド開発では、バックエンド インターフェイスとの対話を処理する必要があることが多く、HTTP ステータス コードはその非常に重要な部分です。 HTTP ステータス コードを理解して取得すると、インターフェイスから返されたデータをより適切に処理できるようになります。この記事では、JavaScript を使用して HTTP ステータス コードを取得する方法と、具体的なコード例を紹介します。 1. HTTP ステータス コードとは何ですか? HTTP ステータス コードとは、ブラウザがサーバーへのリクエストを開始したときに、サービスが
