vue-compile概述


來源 劉濤

Vue的核心可以分為三個大塊:數據處理和雙向綁定模板編譯虛擬dom

前面我們對第一部分的主要內容雙向綁定做了一個分析講解,接下來我們說一說模板編譯。

這一部分的內容比較多,也比較復雜。由於所涉及的情況太多了,我也不可能把每一種情況都覆蓋到。

 

盡量做到既不啰嗦,又能分享更多的內容。前面我們也提到過,模板編譯分為三個階段:

生成ast優化靜態內容生成render

 

官方文檔中提到rendertemplate更加底層,許多人對render函數都不是很明白,

相信通過這一部分的分析,你可以對render基本做到了如指掌

如果你在創建對象時直接傳入render函數,模板編譯這一步就可以直接跳過,這樣效率肯定更高,

 

(react就是不用模板編譯的, 如果vue比react效率高,那就是因為需要編譯jsx?

 

但同時我們編寫代碼的難度會增加很多。實際開發過程中,根據需要,恰當選擇。

 

Vue對象上有一個全局函數compile,在src/entries/web-runtime-with-compiler.js中。

import { compileToFunctions } from 'web/compiler/index' ... ... ... Vue.compile = compileToFunctions

該方法來自src/platforms/web/compiler/index文件。

import { isUnaryTag, canBeLeftOpenTag } from './util' import { genStaticKeys } from 'shared/util' import { createCompiler } from 'compiler/index' import modules from './modules/index' import directives from './directives/index' import { isPreTag, mustUseProp, isReservedTag, getTagNamespace } from '../util/index' export const baseOptions: CompilerOptions = { expectHTML: true, modules, directives, isPreTag, isUnaryTag, mustUseProp, canBeLeftOpenTag, isReservedTag, getTagNamespace, staticKeys: genStaticKeys(modules) } const { compile, compileToFunctions } = createCompiler(baseOptions) export { compile, compileToFunctions }

該文件中主要定義了一個baseOptions,它主要保存了解析模板時和平台相關的一些配置

對應的src/platforms/weex/compiler/index中也有一份名稱一樣的配置。

這里我們簡單說說web相關的這些配置都是什么意思。

 

  • expectHTML。目前具體還不是很明白,weex中沒有改項,從字面意思來看,應該是是否期望HTML

  • modules。包括klassstyle,對模板中類和樣式的解析。

  • directives。這里包括modelv-model)、htmlv-html)、text(v-text)三個指令。

  • isPreTag。是否是pre標簽。

  • isUnaryTag。是否是單標簽,比如imginputiframe等。

  • mustUseProp。需要使用props綁定的屬性,比如valueselected等。

  • canBeLeftOpenTag。可以不閉合的標簽,比如trtd等。

  • isReservedTag。是否是保留標簽html標簽和SVG標簽。

  • getTagNamespace。獲取命名空間svgmath

  • staticKeys。靜態關鍵詞,包括staticClass,staticStyle

上面這些方法或者屬性,在編譯模板時會用到。

這里知識簡單的列出來它們的用途,方便看一眼。

我們來看createCompiler函數的實現。

 

export function createCompiler (baseOptions: CompilerOptions) { const functionCompileCache: { [key: string]: CompiledFunctionResult; } = Object.create(null) // compile 函數的實現 // compileToFunctions 函數的實現 return { compile, compileToFunctions } }

該函數只是compilecompileToFunctions的簡單封裝,開始定義了functionCompileCache

它用來緩存編譯之后的模板,方便之后復用

因為compileToFunctions里面調用了compile,所以我們先看一下compile

 

  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) } if (options) { // merge custom modules if (options.modules) { finalOptions.modules = (baseOptions.modules || []).concat(options.modules) } // merge custom directives if (options.directives) { finalOptions.directives = extend( Object.create(baseOptions.directives), options.directives ) } // copy other options for (const key in options) { 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 }

我們從上往下,依次看看它都做了哪些事兒。

它接收兩個參數templateoptionstemplate不用過多解釋,

options在內部主要是用戶自己定義的delimiters

 

finalOptions繼承自我們上面提到的baseOptions

並添加了一個搜集錯誤的warn方法,然后合並了options傳入的各種配置選項。

modulesdirectives合並方法不同是因為modules是數組,而directives是一個對象。

 

baseCompile中執行的就是模板編譯的三個重要步驟,后面我們會詳細講解。

 

最終,返回編譯之后的對象。

 

function compileToFunctions ( template: string, options?: CompilerOptions, vm?: Component ): CompiledFunctionResult { options = options || {} ... // 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 = [] res.render = makeFunction(compiled.render, fnGenErrors) 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) } 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) }

compileToFunctions函數中,首先從緩存中獲取編譯結果,沒有則調用compile函數來編譯。

在開發環境,我們在這里會拋出編譯過程中產生的錯誤,

最終返回一個含有render函數,和staticRenderFns數組的對象,並把它放在緩存中。

 

這里我們使用makeFunction來創建函數。

function makeFunction (code, errors) { try { return new Function(code) } catch (err) { errors.push({ err, code }) return noop } }

很簡單,就是利用了我們new Function,並搜集了錯誤。

compilecompileToFunctions兩個方法的不同之處有以下幾點。

1、 compile返回的結果中render是字符串,staticRenderFns是字符串組成的數組,而compileToFunctions中把它們變成了函數。

2、 compile返回的結果中,有模板生成的ast和搜集到的錯誤。而compileToFunctions對其結果進行了一些處理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM