【Vue原理】Compile - 源码版 之 从新建实例到 compile结束的主要流程

951 阅读5分钟

写文章不容易,点个赞呗兄弟

专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧 研究基于 Vue版本 【2.5.17】

如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧

【Vue原理】Compile - 源码版 之 从新建实例到 compile结束的主要流程

Compile 的内容十分之多,今天先来个热身,先不研究 compile 内部编译细节,而是记录一下

从新建实例开始,到结束 compile ,其中的大致外部流程,不涉及 compile 的内部流程

或者说,我们要研究 compile 这个函数是怎么生成的

注意,如果你没有准备好,请不要阅读这篇文章

注意哦,会很绕,别晕了

公众号

好的,正文开始

首先,当我们通过 Vue 新建一个实例的时候会调用Vue

公众号

所以从 Vue 函数入手

function Vue(){    

    // .....

    vm.$mount(vm.$options.el);
}

然后内部的其他处理都可以忽视,直接定位到 vm.$mount,就是从这里开始去编译的

继续去查找这个函数

Vue.prototype.$mount = function(el) {    



    var options = this.$options;

   

    if (!options.render) {  

     

        var tpl= options.template;  

   

        // 获取模板字符串
        if (tpl) {    

                // 根据传入的选择器找到元素,然后拿到该元素内的模板

                //  本来有很多种获取方式,但是为了简单,我们简化为一种,知道意思就可以了

            tpl = document.querySelector(tpl).innerHTML;
        }    

     

        if (tpl) {  

            // 生成 render 函数保存

            var ref = compileToFunctions(tpl, {},this);  

          

            // 每一个组件,都有自己的 render

            options.render = ref.render
            options.staticRenderFns =ref.staticRenderFns;
        }
    }      



    // 执行上面生成的 render,生成DOM,挂载DOM,这里忽略不讨论
    return mount.call(this, el)
};

compile 的主要作用就是,根据 template 模板,生成 render 函数

那么到这里,整个流程就走完了,因为 render 已经在这里生成了

公众号

我们观察到

在上面这个函数中,主要就做了三件事

1 获取 template 模板

根据你传入的参数,来各种获取 template 模板

这里应该都看得懂了,根据DOM,或者根据选择器

2 生成 render

通过 compileToFunctions ,传入 template

就可以生成 render 和 staticRenderFns

看着是挺简单哦,就一个 compileToFunctions,但是我告诉你,这个函数的诞生可不是这么容易的,兜兜转转,十分曲折,相当得曲折复杂,没错,这就是我们下面研究的重点

但是这流程其实好像也没有什么帮助?但是如果你阅读源码的话,或许可以对你理清源码有些许帮助吧

再一次佩服 尤大的脑回路

公众号

3 保存 render

保存在 vm.$options 上,用于在后面调用

公众号

下面就来说 compileToFunctions 的诞生史

注意,很绕很绕,做好心理准备

首先我定位到 compileToFunctions,看到下面这段代码

var ref$1 = createCompiler();



// compileToFunctions 会返回 render 函数 以及 staticRenderFns

var compileToFunctions = ref$1.compileToFunctions;

于是我知道

compileToFunctions 是 createCompiler 执行返回的!!

那么继续定位 createCompiler


createCompiler

var createCompiler = createCompilerCreator(  



    function baseCompile(template, options) {    

    

        var ast = parse(template.trim(), options);    

    

        if (options.optimize !== false) {

            optimize(ast, options);
        }    

   

        var code = generate(ast, options);  

     

        return {            

            ast: ast,            

            render: code.render,            

            staticRenderFns: code.staticRenderFns

        }
    }
);

卧槽,又来一个函数,别晕啊兄弟

公众号
不过,注意注意,这里是重点,非常重

公众号

首先明确两点

1、createCompiler 是 createCompilerCreator 生成的

2、给 createCompilerCreator 传了一个函数 baseCompile

baseCompile

这个 baseCompile 就是 生成 render 的大佬

看到里面包含了 渲染三巨头,【parse,optimize,generate】

但是今天不是讲这个的,这三个东西,每个内容都十分巨大

这里先跳过,反正 baseCompile 很重要,会在后面被调用到,得先记着

然后,没错,我们又遇到了一个 函数 createCompilerCreator ,定位它!


createCompilerCreator

function createCompilerCreator(baseCompile) {    



    return function () {

       

        // 作用是合并选项,并且调用 baseCompile
        function compile(template) {  

         

            // baseCompile 就是 上一步传入的,这里执行得到 {ast,render,statickRenderFn}

            var compiled = baseCompile(template);            

            return compiled

        }        

        return {          



            // compile 执行会返回 baseCompile 返回的 字符串 render

            compile: compile,      

     

            // 为了创建一层 缓存闭包,并且闭包保存 compile

            // 得到一个函数,这个函数是 把 render 字符串包在 函数 中
            compileToFunctions: createCompileToFunctionFn(compile)
        }
    }
}

这个函数执行过后,会返回一个函数

很明显,返回的函数就 直接赋值 给了上面讲的的 createCompiler

我们看下这个返回给 createCompiler 的函数里面都干了什么?

生成一个函数 compile

内部存在一个函数 compile,这个函数主要作用是

调用 baseCompile,把 baseCompile 执行结果 return 出去

baseCompile 之前我们强调过的,就是那个生成 render 的大佬

忘记的,可以回头看看,执行完毕会返回

{ render,staticRenderFns }

返回 compileToFunctions 和 compile

其实 返回的这两个函数的作用大致都是一样的

都是为了执行上面那个 内部 compile

但是为什么分出一个 compileToFunctions 呢?

还记得开篇我们的 compileToFunctions 吗

就是那个在 vm.$mount 里我们要探索的东西啊

就是他这个吊毛,生成的 render 和 staticRenderFns

公众号

再看看那个 内部 compile,可以看到他执行完就是返回

{ render, staticRenderFns }

你看,内部 compile 就是 【vm.$mount 执行的 compileToFunctions】 啊

为什么 compileToFunctions 没有直接赋值为 compile 呢!!

因为要做模板缓存!!

可以看到,没有直接让 compileToFunctions = 内部compile

而是把 内部 compile 传给了 createCompileToFunctionFn

没错 createCompileToFunctionFn 就是做缓存的

为了避免每个实例都被编译很多次,所以做缓存,编译一次之后就直接取缓存

createCompileToFunctionFn

来看看内部的源码,缓存的代码已经标红

function createCompileToFunctionFn(compile) {    



    // 作为缓存,防止每次都重新编译

    // template 字符串 为 key , 值是 render 和 staticRenderFns
    var cache = Object.create(null);    



    return function compileToFunctions(template, options, vm) {        

        var key = template;        



        // 有缓存的时候直接取出缓存中的结果即可

        if (cache[key]) return cache[key]  

     

        // compile 是 createCompileCreator 传入的compile

        var compiled = compile(template, options);        

        var res = {            



            // compiled.render 是字符串,需要转成函数

            render : new Function(compiled.render)
            staticRenderFns : compiled.staticRenderFns.map(function(code) {                

                return  new Function(code, fnGenErrors)

            });
        };        



        return (cache[key] = res)

    }
}

额外:render 字符串变成可执行函数

var res = {    

    render: new Function(compiled.render) ,    

    staticRenderFns: compiled.staticRenderFns.map(function(code) {        

        return new Function(code, fnGenErrors)

    });
};

这段代码把 render 字符串可执行函数,因为render生成的形态是一个字符串,如果后期要调用运行,比如转成函数

所以这里使用了 new Function() 转化成函数

同理,staticRenderFns 也一样,只不过他是数组,需要遍历,逐个转化成函数

他的缓存是怎么做的

使用一个 cache 闭包变量

template 为 key

生成的 render 作为 value

当实例第一次渲染解析,就会被存到 cache 中

当实例第二次渲染解析,那么就会从 cache 中直接获取

什么时候实例会解析第二次?

比如 页面A到页面B,页面B又转到页面A。

页面A 这个实例,按理就需要解析两次,但是有缓存之后就不会


理清思路

公众号

也就是说,compileToFunctions 其实内核就是 baseCompile!

不过 compileToFunctions 是经过了 两波包装的 baseCompile

第一波包装在 createCompilerCreator 中的 内部 compile 函数中

内部函数的作用是

合并公共options和 自定义options ,但是相关代码已经省略,

另一个就是执行 baseCompile

第二波包装在 createCompileToFunctions 中,目的是进行 缓存

公众号

公众号