此次,來學習下Vue是如何解析HTML代碼的。
從以前學習 Render 的過程當中咱們知道,template 的編譯在 $mount
方法中出現過。html
// src/platforms/web/entry-runtime-with-compiler.js 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) { return this } const options = this.$options // resolve template/el and convert to render function if (!options.render) { let template = options.template if (template) { if (typeof template === 'string') { if (template.charAt(0) === '#') { // 首字母爲#號,看做是ID。 template = idToTemplate(template) } } else if (template.nodeType) { // 爲真實 DOM,直接獲取html template = template.innerHTML } else { return this } } else if (el) { // 獲取 HTML template = getOuterHTML(el) } if (template) { // 進行編譯並賦值給 vm.$options const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) // 渲染函數 options.render = render // 靜態渲染方法 options.staticRenderFns = staticRenderFns } } return mount.call(this, el, hydrating) }
其實以上代碼總結起來就4步:前端
關鍵在於第三步,編譯 render 函數上。先獲取 template,即獲取HTML內容,而後執行 compileToFunctions 來編譯,最後將 render 和 staticRenderFns 傳給 vm.$options 對象。
順便看看這兩個方法都用在哪裏?vue
// src/core/instance/render.js Vue.prototype._render = function (): VNode { try { vnode = render.call(vm._renderProxy, vm.$createElement) } catch (e) { handleError(e, vm, `render`) } return vnode }
// src/core/instance/render-helpers/render-static.js export function renderStatic ( index: number, isInFor: boolean ): VNode | Array<VNode> { const cached = this._staticTrees || (this._staticTrees = []) let tree = cached[index] if (tree && !isInFor) { return tree } // otherwise, render a fresh tree. tree = cached[index] = this.$options.staticRenderFns[index].call( this._renderProxy, null, this ) markStatic(tree, `__static__${index}`, false) return tree }
因而可知,template 編譯生成的方法都用在了渲染行爲中。node
下面咱們順着編譯代碼往下找。在 mount 方法中執行了 compileToFunctions
方法。git
const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this)
找到方法的所在之處:github
// src/platforms/web/compiler/index.js const { compile, compileToFunctions } = createCompiler(baseOptions)
// src/compiler/index.js export const createCompiler = createCompilerCreator(function baseCompile ( template: string, options: CompilerOptions ): CompiledResult { // 將template轉爲AST語法樹對象 const ast = parse(template.trim(), options) if (options.optimize !== false) { // 優化 optimize(ast, options) } // 生成渲染代碼 const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } })
先看裏面的 baseCompile 方法,其做用爲將 HTML 字符串轉爲 AST 抽象語法樹對象,並進行優化,最後生成渲染代碼。返回值中 render 爲渲染字符串,staticRenderFns 爲渲染字符串數組。
以後再來看看 createCompilerCreator 方法:web
// src/compiler/create-compiler.js export function createCompilerCreator (baseCompile: Function): Function { return function createCompiler (baseOptions: CompilerOptions) { 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 || null), 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 } return { compile, compileToFunctions: createCompileToFunctionFn(compile) } } }
來看 compile 方法:合併 option 配置參數,而後執行外部傳入的 baseCompile 方法,返回方法執行的返回結果。最終返回 { compile, compileToFunctions }
,
createCompileToFunctionFn 代碼以下:正則表達式
export function createCompileToFunctionFn (compile: Function): Function { // 定義緩存 const cache = Object.create(null) return function compileToFunctions ( template: string, options?: CompilerOptions, vm?: Component ): CompiledFunctionResult { options = extend({}, options) const warn = options.warn || baseWarn delete options.warn // 確認緩存,有緩存直接返回 const key = options.delimiters ? String(options.delimiters) + template : template if (cache[key]) { return cache[key] } // compile const compiled = compile(template, options) // turn code into functions const res = {} const fnGenErrors = [] // 生成 render 和 staticRenderFns 方法 res.render = createFunction(compiled.render, fnGenErrors) res.staticRenderFns = compiled.staticRenderFns.map(code => { return createFunction(code, fnGenErrors) }) // 返回方法並緩存 return (cache[key] = res) } }
這裏就找到了咱們在 mount 方法中看到的 render 和 staticRenderFns 方法了。createCompileToFunctionFn 方法其實就是將傳入的 render 和 staticRenderFns 字符串轉爲真實方法。編程
至此,捋一下思路:
template的編譯用於render渲染行爲中,因此template最後生成渲染函數。
template 的解析過程當中數組
其實 const { compile, compileToFunctions } = createCompiler(baseOptions)
就是 createCompilerCreator 的返回結果。因此,在 mount 中使用的 compileToFunctions 方法就是 createCompileToFunctionFn 方法生成的。
總體思路濾清了,來看看關鍵的 baseCompile 方法。該方法進行了三步操做:
先來說講AST抽象語法樹。維基百科的解釋是:
在計算機科學中,抽象語法樹(abstract syntax tree或者縮寫爲AST),或者語法樹(syntax tree),是源代碼的抽象語法結構的樹狀表現形式,這裏特指編程語言的源代碼。
parse 方法的最終目的就是將 template 解析爲 AST 元素對象。在 parse 解析方法中,用到了大量的正則。正則的具體用法以前寫過一篇文章:一塊兒來理解正則表達式。代碼量不少,考慮了各類解析的狀況。這裏不贅述太多,找一條主線來學習,其餘內容我將在項目中註釋。
來看看 parse 方法。
export function parse ( template: string, options: CompilerOptions ): ASTElement | void { // 定義了各類參數和方法 parseHTML(template, { warn, expectHTML: options.expectHTML, isUnaryTag: options.isUnaryTag, canBeLeftOpenTag: options.canBeLeftOpenTag, shouldDecodeNewlines: options.shouldDecodeNewlines, shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref, shouldKeepComment: options.comments, start (tag, attrs, unary) {}, end () {} chars (text: string) {}, comment (text: string) {} ) return root }
實際上 parse 就是 parseHTML 的過程,最後返回AST元素對象。其中,傳入的 options 配置對象中,start、end、chars、comment方法都會在 parseHTML 方法中用到。其實相似於生命週期鉤子,在某個階段執行。
parseHTML 方法是正則解析HTML的過程,這部分我將在以後的博客中單獨說下,也能夠看項目的註釋,將不定時更新項目註釋。
該方法只是作了些標記靜態節點的行爲,目的是爲了在從新渲染時不重複渲染靜態節點,以達到性能優化的目的。
export function optimize (root: ?ASTElement, options: CompilerOptions) { if (!root) return isStaticKey = genStaticKeysCached(options.staticKeys || '') isPlatformReservedTag = options.isReservedTag || no // 標記全部非靜態節點 markStatic(root) // 標記靜態根節點 markStaticRoots(root, false) }
generate 方法用於將 AST 元素生成 render 渲染字符串。
export function generate ( ast: ASTElement | void, options: CompilerOptions ): CodegenResult { const state = new CodegenState(options) const code = ast ? genElement(ast, state) : '_c("div")' return { render: `with(this){return ${code}}`, staticRenderFns: state.staticRenderFns } }
最後生成以下這樣的渲染字符串:
with(this){return _c('div',{attrs:{"id":"app"}},[_c('button',{on:{"click":hey}},[_v(_s(message))])])}
其中的 _c _v _s 等方法在哪裏呢~這個咱們以前提及過:
// src/core/instance/render.js // 建立vnode元素 vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) // src/core/instance/render-helper/index.js export function installRenderHelpers (target: any) { target._o = markOnce target._n = toNumber target._s = toString target._l = renderList target._t = renderSlot target._q = looseEqual target._i = looseIndexOf target._m = renderStatic target._f = resolveFilter target._k = checkKeyCodes target._b = bindObjectProps target._v = createTextVNode target._e = createEmptyVNode target._u = resolveScopedSlots target._g = bindObjectListeners }
其實template部分真的內容展開超級多,以後會展開細說。本來計劃大前天就把博客寫出來的,結果看代碼看着看着繞進去了。因此,仍是那句話,看代碼得抓住主線,帶着問題去看,不要在乎細枝末節。
這也算是個人經驗教訓了,之後每次看代碼,牢記待着明確的問題去看去解決。想一次看懂整個項目的代碼是不可行的。
下期預告,parseHTML 細節解析
鑑於前端知識碎片化嚴重,我但願可以系統化的整理出一套關於Vue的學習系列博客。
本文源碼已收入到GitHub中,以供參考,固然能留下一個star更好啦^-^。
https://github.com/violetjack/VueStudyDemos
VioletJack,高效學習前端工程師,喜歡研究提升效率的方法,也專一於Vue前端相關知識的學習、整理。
歡迎關注、點贊、評論留言~我將持續產出Vue相關優質內容。
新浪微博: http://weibo.com/u/2640909603
掘金:https://gold.xitu.io/user/571...
CSDN: http://blog.csdn.net/violetja...
簡書: http://www.jianshu.com/users/...
Github: https://github.com/violetjack