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

38次阅读

共计 4473 个字符,预计需要花费 12 分钟才能阅读完成。

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

专注 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 中,目的是进行 缓存

正文完
 0