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

Posted 神仙朱

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue原理Compile - 源码版 之 从新建实例到 compile结束的主要流程相关的知识,希望对你有一定的参考价值。

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

研究基于 Vue版本2.5.17


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

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

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

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

注意哦,会很绕,别晕了

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

好的,正文开始

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

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


所以从 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 已经在这里生成了

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


我们观察到

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


1 获取 template 模板

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

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


2 生成 render 

通过 compileToFunctions ,传入 template

就可以生成  render 和 staticRenderFns 

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

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

再一次佩服 尤大的脑回路

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


3 保存 render

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

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


下面就来说 compileToFunctions 的诞生史

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

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

var ref$1 = createCompiler();


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

var compileToFunctions = ref$1.compileToFunctions;

于是我知道

compileToFunctions 是 createCompiler 执行返回的!!

那么继续定位 createCompiler


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

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

       }
   }
);

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

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

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

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

首先明确两点

1、createCompiler 是 createCompilerCreator 生成的

2、给 createCompilerCreator 传了一个函数 baseCompile


baseCompile 

这个 baseCompile 就是 生成 render 的大佬

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

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

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

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


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

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


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


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

{ render, staticRenderFns }

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

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


因为要做模板缓存!!

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

而是把 内部 compile 传给了 createCompileToFunctionFn 

没错 createCompileToFunctionFn  就是做缓存的

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



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

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 这个实例,按理就需要解析两次,但是有缓存之后就不会



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

理清思路 

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


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

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

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

内部函数的作用是

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

另一个就是执行 baseCompile

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


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


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

最后

鉴于本人能力有限,难免会有疏漏错误的地方,请大家多多包涵,如果有任何描述不当的地方,欢迎后台联系本人,领取红包


以上是关于Vue原理Compile - 源码版 之 从新建实例到 compile结束的主要流程的主要内容,如果未能解决你的问题,请参考以下文章

.6-Vue源码之AST

Vue双向绑定的实现原理系列:补充指令解析器compile

Vue源码之响应式原理(个人向)

vue编译原理之vue-template-compiler

Vue源码Compile.js

VUE 响应式原理源码:带你一步精通 VUE | 原力计划