Vue.js 编译模板的过程
Vue.js 编译模板的过程主要分为两个阶段:解析(Parse)和生成(Generate)。
1、解析阶段:这个阶段的主要任务是将模板字符串转换为抽象语法树(AST)。抽象语法树是一种以树状的形式表现源代码结构的模型。在 Vue.js 中,解析器会将模板字符串解析为一棵 AST,每个节点都是一个普通的 JavaScript 对象,这个对象描述了元素/文本节点的各种属性。
2、生成阶段:这个阶段的主要任务是将 AST 转换为渲染函数。渲染函数的主要任务是将模板转换为 Virtual DOM,也就是说,渲染函数的返回值是 Virtual DOM。
这个过程的主要步骤如下:
1、Vue 接收到模板字符串。
2、Vue 使用解析器(Parser)将模板字符串解析为 AST。
3、Vue 使用优化器(Optimizer)标记静态节点。这个步骤不是必须的,但是它可以提高后续的 patch 过程。
4、Vue 使用代码生成器(Code Generator)将 AST 转换为渲染函数。
这个过程是 Vue.js 的编译设计的精髓,它使得 Vue.js 可以提供类似于原生 JavaScript 的性能,同时还能提供一个简单易用的模板语法。
vue-template-compiler是编译vue模板的包,传入模板返回AST抽象语法树。
const compiler = require('vue-template-compiler')
const val = compiler.compile('<span class="active" :total="count">666</span>')
输出结果如下:
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: []
}
可以看到对象中有ast属性和render函数,其实ast是为了生成render函数用的。
with (this) {
return _c(
'span',
{ staticClass: "active", attrs: { "total": count } },
[_v("666")]
)
}
render函数会调用很多辅助的函数,例如 _c,_v 那么这些都来自哪里呢?
其实是渲染时候用的的辅助函数,源码路径 https://github.com/vuejs/vue/blob/dev/src/core/instance/render-helpers/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
target._d = bindDynamicKeys
target._p = prependModifier
}
发现上面并没有_c,我们继续寻找源码可以发现在 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 就是创建虚拟节点 VNode。路径 https://github.com/vuejs/vue/blob/dev/src/core/vdom/create-element.js
那么至此,我们大致了解了 createElement 创建 VNode 的过程,每个 VNode 有 children,children 每个元素也是一个 VNode,这样就形成了一个 VNode Tree,它很好的描述了我们的 DOM Tree。