1

学习目标

  • 模板编译整体过程
  • 组件化机制

源码环境

  "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的钩子

学习资料

源码分为三块进行了整理:
可以在主页一一查看。


哦哈哈
26 声望9 粉丝

没比别人多什么天赋;只有努力💪、用心、重复