源码解析(三)——模板编译、组件化机制

学习目标

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

源码环境

  "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  
    }  
})
什么是静态节点
两级包裹,纯文本,比如:
静态节点

//静态根节点里面是静态节点
递归找到静态根节点,如果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()声明的组建可以全局使用

例如:

   
    
https://www.processon.com/vie...

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

你可能感兴趣的:(vue.js,源码分析,源码学习,组件化,模板方法模式)