Vue.js 編譯模板的過(guò)程

Vue.js 編譯模板的過(guò)程主要分為兩個(gè)階段:解析(Parse)和生成(Generate)。
1、解析階段:這個(gè)階段的主要任務(wù)是將模板字符串轉(zhuǎn)換為抽象語(yǔ)法樹(AST)。抽象語(yǔ)法樹是一種以樹狀的形式表現(xiàn)源代碼結(jié)構(gòu)的模型。在 Vue.js 中,解析器會(huì)將模板字符串解析為一棵 AST,每個(gè)節(jié)點(diǎn)都是一個(gè)普通的 JavaScript 對(duì)象,這個(gè)對(duì)象描述了元素/文本節(jié)點(diǎn)的各種屬性。
2、生成階段:這個(gè)階段的主要任務(wù)是將 AST 轉(zhuǎn)換為渲染函數(shù)。渲染函數(shù)的主要任務(wù)是將模板轉(zhuǎn)換為 Virtual DOM,也就是說(shuō),渲染函數(shù)的返回值是 Virtual DOM。
這個(gè)過(guò)程的主要步驟如下:
1、Vue 接收到模板字符串。
2、Vue 使用解析器(Parser)將模板字符串解析為 AST。
3、Vue 使用優(yōu)化器(Optimizer)標(biāo)記靜態(tài)節(jié)點(diǎn)。這個(gè)步驟不是必須的,但是它可以提高后續(xù)的 patch 過(guò)程。
4、Vue 使用代碼生成器(Code Generator)將 AST 轉(zhuǎn)換為渲染函數(shù)。
這個(gè)過(guò)程是 Vue.js 的編譯設(shè)計(jì)的精髓,它使得 Vue.js 可以提供類似于原生 JavaScript 的性能,同時(shí)還能提供一個(gè)簡(jiǎn)單易用的模板語(yǔ)法。
vue-template-compiler是編譯vue模板的包,傳入模板返回AST抽象語(yǔ)法樹。
const compiler = require('vue-template-compiler')const val = compiler.compile('<span class="active" :total="count">666</span>')
輸出結(jié)果如下:
const res = {ast: {type: 1,tag: 'span',attrsList: [ { name: 'total', value: 'count' } ],attrsMap: { class: 'active', ':total': 'count' },rawAttrsMap: {},parent: undefined,children: [ { type: 3, text: 666, static: true } ],plain: false,staticClass: '"active"',hasBindings: true,attrs: [ { name: 'total', value: 'count', dynamic: false } ],static: false,staticRoot: false},render: `with(this){return _c('span',{staticClass:"active",attrs:{"total":count}},[_v("666")])}`,staticRenderFns: [],errors: [],tips: []}
可以看到對(duì)象中有ast屬性和render函數(shù),其實(shí)ast是為了生成render函數(shù)用的。
with (this) {return _c('span',{ staticClass: "active", attrs: { "total": count } },[_v("666")])}
render函數(shù)會(huì)調(diào)用很多輔助的函數(shù),例如 _c,_v 那么這些都來(lái)自哪里呢?
其實(shí)是渲染時(shí)候用的的輔助函數(shù),源碼路徑 https://github.com/vuejs/vue/blob/dev/src/core/instance/render-helpers/index.js
export function installRenderHelpers (target: any) {target._o = markOncetarget._n = toNumbertarget._s = toStringtarget._l = renderListtarget._t = renderSlottarget._q = looseEqualtarget._i = looseIndexOftarget._m = renderStatictarget._f = resolveFiltertarget._k = checkKeyCodestarget._b = bindObjectPropstarget._v = createTextVNodetarget._e = createEmptyVNodetarget._u = resolveScopedSlotstarget._g = bindObjectListenerstarget._d = bindDynamicKeystarget._p = prependModifier}
發(fā)現(xiàn)上面并沒有_c,我們繼續(xù)尋找源碼可以發(fā)現(xiàn)在 initRender 這里,路徑 https://github.com/vuejs/vue/blob/dev/src/core/instance/render.js
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
createElement 就是創(chuàng)建虛擬節(jié)點(diǎn) VNode。路徑 https://github.com/vuejs/vue/blob/dev/src/core/vdom/create-element.js
那么至此,我們大致了解了 createElement 創(chuàng)建 VNode 的過(guò)程,每個(gè) VNode 有 children,children 每個(gè)元素也是一個(gè) VNode,這樣就形成了一個(gè) VNode Tree,它很好的描述了我們的 DOM Tree。
