学习目标
- 模板编译整体过程
- 组件化机制
源码环境
"name": "vue", "version": "2.6.11",
源码的目录结构,可以看下vue源码解析(一)——初始化流程及数据响应式过程梳理
我将源码分析分为三个模块进行了分析,可以互相结合看下,学习起来更轻松下。
模板编译整体过程
解析
编辑器可以将模板解析成AST抽象语法树;通过遍历这颗对象树生成代码
路径:src/compiler/index.js
export const createCompiler = createCompilerCreator(function baseCompile ( template: string, options: CompilerOptions ): CompiledResult { // 1.将html字符串解析成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 } })
parse()路径:src/compiler/parser/index.js
export function parse ( template: string, options: CompilerOptions ): ASTElement | void { // 开始编译 解析相关变量 const stack = [] // 解析html的相关代码 parseHTML(template, { // 遇到起始标签调用 start (tag, attrs, unary, start, end) { ....... // 遇到一个起始节点就创建一个ASTElement元素,单根的一个对象 let element: ASTElement = createASTElement(tag, attrs, currentParent) if (ns) { element.ns = ns } ....... if (inVPre) { processRawAttrs(element) } else if (!element.processed) { // for if noce 需要提前处理 processFor(element) processIf(element) processOnce(element) } ...... }, })
parseHTML路径:src/compiler/parser/index.js
因为要匹配HTML,所以里面都是正则表达式,各种匹配
优化
优化器的作用是在AST中找出静态子树并打上标记。静态子树是在AST中永远不变的节点,如纯文本节点。
路径:src/compiler/index.js
export const createCompiler = createCompilerCreator(function baseCompile ( template: string, options: CompilerOptions ): CompiledResult { const ast = parse(template.trim(), options) 2.优化的核心目标,标记静态节点 if (options.optimize !== false) { optimize(ast, options) } const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } })
什么是静态节点
两级包裹,纯文本,比如:
<div>静态<span>节点</span></div>
//静态根节点里面是静态节点
递归找到静态根节点,如果children 也是静态节点,那就定义为静态节点。未来就会生成静态函数,在做patch的之后会跳过vue的处理工作方式:官方认为,如果只有一标签,消耗有些大,去占用内存。所以静态节点指的两层都为静态节点
代码生成
将AST转换成渲染函数中的内容,即代码字符串。
路径:src/compiler/index.js
export const createCompiler = createCompilerCreator(function baseCompile ( template: string, options: CompilerOptions ): CompiledResult { const ast = parse(template.trim(), options) if (options.optimize !== false) { optimize(ast, options) } 3.将ast生成为函数字符串,还不是函数,而是函数的字符串 const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } })
generate路径:src/compiler/codegen/index.js
export function generate ( ast: ASTElement | void, options: CompilerOptions ): CodegenResult { export function genElement (el: ASTElement, state: CodegenState): string { if (el.parent) { el.pre = el.pre || el.parent.pre } // 从这里可以看出 for的优先级比if高 // 一个标签从if 和 for 首先执行for 肚子里包着if 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 { } }
genFor路径:src/compiler/codegen/index.js
export function genFor ( el: any, state: CodegenState, altGen?: Function, altHelper?: string ): string { // v-for="item in items" // exp===items // el.alias === item const exp = el.for const alias = el.alias const iterator1 = el.iterator1 ? `,${el.iterator1}` : '' // index const iterator2 = el.iterator2 ? `,${el.iterator2}` : '' //key?? ......... el.forProcessed = true // avoid recursion // 最后生成的代码是一串子字符串 // altHelper 通常情况不存在 // _l(exp,functin(alias,iterator1,iterator2){return }) return `${altHelper || '_l'}((${exp}),` + `function(${alias}${iterator1}${iterator2}){` + `return ${(altGen || genElement)(el, state)}` + '})' }
组件化机制
组件声明:Vue.component()
去寻找为什么Vue.component()声明的组建可以全局使用
例如:
<body> <div id="demo> <comp></comp> </div> </body> vue.component('comp',{ template:'<div>this is comp</div>' }) const app= new Vue({ el:'#demo' })
initAssetRegisters()路径:src/core/global-api/assets.js
export function initAssetRegisters (Vue: GlobalAPI) { /** * Create asset registration methods. */ // ASSET_TYPES是一个数组:['component', 'directive', 'filter'] ASSET_TYPES.forEach(type => { //拿 component举例: vue.component = function('id=comp',){} Vue[type] = function ( id: string, definition: Function | Object ): Function | Object | void { if (!definition) { return this.options[type + 's'][id] } else { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && type === 'component') { validateComponentName(id) } //组建声明的相关代码 if (type === 'component' && isPlainObject(definition)) { definition.name = definition.name || id // _base是Vue // Vue.extend({}) 返回的是构造函数 把组建的配置项转化为组建的构造函数 definition = this.options._base.extend(definition) } if (type === 'directive' && typeof definition === 'function') { definition = { bind: definition, update: definition } } // 注册到components 选项中去, // 全局组建,就是在根组建上去创建一个组建挂载在components里 // 在vue原始选项上添加组建配置,将来其他组件继承,他们都会有这些组建注册 this.options[type + 's'][id] = definition // 如果使用的是component 最终返回的是 组建的构造函数 return definition } } }) }
vm._render()路径:src/core/instance/render.js
import { createElement } from '../vdom/create-element' export function initRender (vm: Component) { .......... // 编译器生成渲染函数使用 vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) // normalization is always applied for the public version, used in // user-written render functions. // 用户编写渲染函数使用 vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) } export function renderMixin (Vue: Class<Component>) { ..... Vue.prototype._render = function (): VNode { const vm: Component = this const { render, _parentVnode } = vm.$options ..... vm.$vnode = _parentVnode // render self let vnode try { currentRenderingInstance = vm // render 函数执行的的时候,传入的参数$createElement,也就是才使用的时候的小h vnode = render.call(vm._renderProxy, vm.$createElement) } catch (e) { ...... } finally { ...... } vnode.parent = _parentVnode return vnode } }
createElement路径:src/core/vdom/create-element.js
// 例如我们用的 h函数(‘div',{},[child1,child2]) export function createElement ( context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean ): VNode | Array<VNode> { // h('div') 或者 h(Component) if (typeof tag === 'string') { let Ctor ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) // 比如 传进来的是,保留标签,原生元素:div、p标签 if (config.isReservedTag(tag)) { // platform built-in elements ............ // 直接new一下直接生成 vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ) } // 尝试拿出构造函数 //isDef(Ctor = resolveAsset(context.$options, 'components', tag) //resolveAsset 查找获取当前的组建 //context.$options 组建实例的上下文中的 ‘components’ 这个属性中去拿tag(当前的标签) // vue.component 调用的时候在 已经在选项中注册了一个‘component',值是一个构造函数,这个标签tag所对应的值就是构造函数 else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { // component // 自定义组建的处理 //Ctor 构造函数 vnode = createComponent(Ctor, data, context, children, tag) } else {......} }
createComponent路径:src/core/vdom/create-component.js
//返回自定义组建的vnode export function createComponent ( Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string ): VNode | Array<VNode> | void { ...... // 数据处理 data = data || {} ...... // 双向绑定的数据处理 if (isDef(data.model)) { transformModel(Ctor.options, data) } // 事件处理 组建上可能会有事件的监听,不是真正的去做事件监听;如果有事件绑定移到合适的选项上去 const listeners = data.on // 如果是抽象组建如何处理 if (isTrue(Ctor.options.abstract)) {} // 安装组建管理钩子: 未来会做组建的初始化(实例创建,挂载); createElement 中会执行这个钩子 installComponentHooks(data) // 创建当前组建的vnode const vnode = new VNode( // 创建的名称 vue-component 开头;-id `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`, data, undefined, undefined, undefined, context, { Ctor, propsData, listeners, tag, children }, asyncFactory ) // 把当前的自定义组件的虚拟节点,把它返回了 return vnode // 虚拟dome 生成真实dome 还是需要patch }
installComponentHooks()路径:src/core/vdom/create-component.js
// 给data中添加一些钩子函数,未来等待patch的时候调用 // 用户传入的钩子和默认钩子的合并function installComponentHooks (data: VNodeData) { const hooks = data.hook || (data.hook = {}) // hooksToMerge 等待合并的钩子 for (let i = 0; i < hooksToMerge.length; i++) { const key = hooksToMerge[i] const existing = hooks[key] const toMerge = componentVNodeHooks[key] if (existing !== toMerge && !(existing && existing._merged)) { hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge } } }
componentVNodeHooks[key]路径:src/core/vdom/create-component.js
const componentVNodeHooks = { // 实例化 挂载 init (vnode: VNodeWithData, hydrating: boolean): ?boolean {} if ( vnode.componentInstance && !vnode.componentInstance._isDestroyed && vnode.data.keepAlive ) { // kept-alive components, treat as a patch // 缓存的情况 const mountedNode: any = vnode // work around flow componentVNodeHooks.prepatch(mountedNode, mountedNode) } else { // 全新的组建创建的情况 // 创建组建实例 const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ) //子组建挂载 child.$mount(hydrating ? vnode.elm : undefined, hydrating) } }, //预打包 预补丁 打补丁之前执行prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {}, //被插入之后执行 insert (vnode: MountedComponentVNode) {}, //被销毁时执行 destroy (vnode: MountedComponentVNode) {} }
首次初始化的时候会触发createElm路径:src/core/vdom/patch.js
// 组建或者元素的创建 function createElm ( // 将vnode 变成真实的dome vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index ) { // 自定义组建创建 if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return } // 后续都是原生标签 const data = vnode.data const children = vnode.children const tag = vnode.tag if (isDef(tag)) { vnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) // 把当前标签创建一个元素 : nodeOps.createElement(tag, vnode) setScope(vnode) //设置样式 } if (__WEEX__) { // 创建一个元素 设置完样式之后,进行递归 孩子去创建 createChildren(vnode, children, insertedVnodeQueue) if (appendAsTree) { if (isDef(data)) { // 处理 事件 属性的初始化 invokeCreateHooks(vnode, insertedVnodeQueue) } // 把节点插入 insert(parentElm, vnode.elm, refElm) } } } }
createComponent路径:src/core/vdom/patch.js
// 自定义组建的创建过程 function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) { // 获取数据 let i = vnode.data if (isDef(i)) { //在data中如果有定义, // 缓存情况, 会看实例是否已经存在,keepAlive的情况 const isReactivated = isDef(vnode.componentInstance) && i.keepAlive // 如果不存在,就需要初始化一下 init if (isDef(i = i.hook) && isDef(i = i.init)) { i(vnode, false /* hydrating */) // 这里面就是调用 init的钩子 } // 上面创建过程已完成,组建实例已存在 if (isDef(vnode.componentInstance)) { // 初始化组建:组建上面有事件,属性。都需要提前执行,initComponent initComponent(vnode, insertedVnodeQueue) // 插入元素 插入dom insert(parentElm, vnode.elm, refElm) if (isTrue(isReactivated)) { reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm) } return true } } }
从这里可以得出,父组建和子组建生命周期触发的时间段
1.parent.create
2.parent.beformount
3.child.create
4.child.beformount
5.parent.mounted
创建自上而下 挂载自下而上
组件化流程总结
1.注册: Vue.component(id,{})
2.实例化和挂载
-create-element.js | cereateElement() 可以按标签类型处理
-create-component.js | createComponent()-> 自定义组件vnode
3.转化为dom
patch
createElm
createComponent
执行 createComponent()里面的init hook的钩子
学习资料
- 思维导图:https://www.processon.com/vie...
源码分为三块进行了整理:
可以在主页一一查看。