业精于勤 荒于嬉
模板编译的主要目标是将模板(template)转换为渲染函数(render)
template => render()
Vue 2.0 需要用到VNode 描述视图以及各种交互,手写显然不切实际,因此用户需要编写类似HTML 代码的Vue模板,通过编译器将模板转换为可返回VNode 的 render 函数。
如果用户设置了template 或者是el 选项,最终就会执行编译函数,compileToFunctions
若指定 template 或 el 选项,则会执行编译, platforms\web\entry-runtime-with-compiler.js编译过程
编译分为三步:解析 ,优化,生成; src\compiler\index.js
export const createCompiler = createCompilerCreator(function baseCompile( template: string, options: CompilerOptions ): CompiledResult { // 1、解析:将模板转换为对象 AST(抽象语法树) const ast = parse(template.trim(), options) //2、标记静态节点(页面中不会变的地方) if (options.optimize !== false) { optimize(ast, options) } //3、代码生成 转换 ast 为代码字符串 new Function(code) 这样就可以得到一个真正的render 函数了 const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } }) 解析-parse 解析器将模板解析为抽象语法树,基于AST可以做优化或者代码生成工作。 调试查看得到的AST, /src/compiler/parser/index.js, 结构如下: 解析器内部分了 HTML 解析器 、 文本解析器 和 过滤器解析器 ,最主要是 HTML 解析器 优化-optimize 优化器的作用是在AST中找出静态子树并打上标记。静态子树是在AST中永远不变的节点,如纯文本节点 标记静态子树的好处: 每次重新渲染,不需要为静态子树创建新节点虚拟DOM 中 patch 时,可以跳过静态子树代码实现,src/compiler/optimizer.js - optimize
标记结束:代码生成 - generate
将AST 转换成渲染函数中的内容,即代码字符串。
generate方法生成渲染函数代码,src/compiler/codegen/index.js
生成的代码形如 `_c('div',{attrs:{"id":"demo"}},[ _c('h1',[_v("Vue.js测试")]), _c('p',[_v(_s(foo))]) ])` 其中 几个关键指令的解析 如 v-if v-for 的解析生成 // 关键指令解析 if (inVPre) { processRawAttrs(element) } else if (!element.processed) { // structural directives processFor(element) processIf(element) processOnce(element) } export function genElement (el: ASTElement, state: CodegenState): string { if (el.parent) { el.pre = el.pre || el.parent.pre } if (el.staticRoot && !el.staticProcessed) { return genStatic(el, state) } else if (el.once && !el.onceProcessed) { return genOnce(el, state) } else if (el.for && !el.forProcessed) { return genFor(el, state) } else if (el.if && !el.ifProcessed) { return genIf(el, state) } else if (el.tag === 'template' && !el.slotTarget && !state.pre) { return genChildren(el, state) || 'void 0' } else if (el.tag === 'slot') { return genSlot(el, state) } else { // component or element let code if (el.component) { code = genComponent(el.component, el, state) } else { let data if (!el.plain || (el.pre && state.maybeComponent(el))) { data = genData(el, state) } const children = el.inlineTemplate ? null : genChildren(el, state, true) code = `_c('${el.tag}'${ data ? `,${data}` : '' // data }${ children ? `,${children}` : '' // children })` } // module transforms for (let i = 0; i < state.transforms.length; i++) { code = state.transforms[i](el, code) } return code } }Vue.component()
initAssetRegisters(Vue) src/core/global-api/assets.js
组件注册使用extend方法将配置转换为构造函数并添加到components选项
_createElement src\core\vdom\create-element.js
_createElement实际执行VNode创建的函数,由于传入tag是非保留标签,因此判定为自定义组件通过createComponent去创建
createComponent src/core/vdom/create-component.js 创建组件 VNode ,保存了上一步处理得到的组件构造函数, props ,事件等注意组件钩子安装和组件tag指定规则
根组件执行更新函数时,会递归创建子元素和子组件,入口createElm
createEle() core/vdom/patch.js
首次执行_update()时,patch()会通过createEle()创建根元素,子元素创建研究从这里开始
createComponent core/vdom/patch.js
// 组件实例创建、挂载 if (isDef(i = i.hook) && isDef(i = i.init)) { i(vnode, false /* hydrating */ ) } // after calling the init hook, if the vnode is a child component // it should've created a child instance and mounted it. the child // component also has set the placeholder vnode's elm. // in that case we can just return the element and be done. if (isDef(vnode.componentInstance)) { // 元素引用指定vnode.elm 元素属性创建等 initComponent(vnode, insertedVnodeQueue) // 插入到父元素 insert(parentElm, vnode.elm, refElm) if (isTrue(isReactivated)) { reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm) } return true }注:
组件创建顺序 自上而下
组件挂载顺序 自下而上
Vue 源码的学习进阶目前先到这里,还需要自己在多看多分析多尝试,Vue源码的学习使我们能够深入理解原理,解答很多开发中的疑惑、规避很多潜在的错误,写出更好的代码。学习大神的代码,能够学习编程思想,设计模式,训练基本功能,提升内里。
人生当自勉 学习需坚持