Vue2.0是如何做到从浏览器不可识别到可识别的呢?(compile阶段源码解析)

2,035 阅读4分钟

前言

vue 的构建其实是分为了两种版本,即完整版本: runtime+compiler 和 运行时版本: runtime 。这两种版本各有什么不同呢?

让我们打开 Vue官网 可以看到Vue给我们提供了一个 compile 函数,通过传入html的字符串就可以获得两个渲染函数。

Untitled.png 注意加粗部分的 完整版时可用 表达的意思就是只能在runtime+compiler的构建模式下才能使用。

由于完整版需要编译器的参与,所以传统的html中的js如果都这么写的话那么每次项目运行时编译就会耗费很长的时间。

我们现在大多数项目( webpack\vite 等在内的打包工具下)基本都是只有runtime阶段,而compile阶段则是由像 vue-loader 这种插件帮我们在项目运行的预编译阶段就已经做了

 但是为了更好的学(面)习(试)源(吹)码(牛) 🤩, 所以我们还是需要了解其中的原理以及学习其中良好的代码风格。

正文

首先准备好vue2.x的源码,由于版本不同代码可能不太相同,这里使用的版本是 2.6.14 

我们知道vue的编译分为了三个阶段:

  • 模版解析阶段 :即将一堆模板字符串用正则等方式解析成抽象语法树AST(解析器)

  • 优化阶段:遍历AST,找出其中的静态节点,并打上标记(优化器)

  • 代码生成阶段:将AST转换成渲染函数(代码生成器)

下面我们将根据源码中文件的调用顺序一步一步的了解 compile 阶段,vue到底做了哪些工作。

多图警告⚠️ 恐图者甚!!!

入口文件

首先进入到入口文件src/platforms/web/entry-runtime-with-compiler.js 

Untitled 1.png

  1. 这里首先通过判断有没有 render函数,如果有了那就直接通过render函数返回dom。

  2. 反之则获取template模版。重点看图片注释

  3. 然后就走到了 compileToFunctions 函数部分,返回了render函数和staticRender函数用于生成 VDOM 

生成Render函数

进入到 compileToFunctions 函数中,发现调用了 createCompiler 函数

Untitled 2.png

随后进入到 createCompiler 函数中,发现通过又调用了 createCompilerCreator 函数。

Untitled 3.png

这个函数返回了一个对象 CompiledResult 用于返回render和staticRender和ast等属性,这个对象的类型如下:


declare type CompiledResult = {

  ast: ?ASTElement;

  render: string;

  staticRenderFns: Array<string>;

  stringRenderFns?: Array<string>;

  errors?: Array<string | WarningMessage>;

  tips?: Array<string | WarningMessage>;

};

进入到 createCompilerCreator 函数发现通过函数闭包,返回了一个对象其中有两个函数, compile函数 和 compileToFunctions函数 ,compile函数中,执行createCompilerCreator 传入的函数参数 baseCompile 进行编译的三个流程。其中具体的代码如下。

Untitled 4.png

所以最重要的就是这个 compile 函数,我们也可以看到返回的第二个函数也调用了一下这个compile,其实作用就是使这个 compileToFunctions 具有编译生成 render函数的作用。

由此这个函数就开始了编译的三个阶段

Untitled 5.png

  • Vue通过HTMLParse这个库将template模版解析成AST树。

  • 通过optimize标记静态节点作为常量,并在使得在VDOM diff时不在更新。

  • 通过 generate 函数生成 render函数和 staticRenderFns 函数用于后面生成虚拟DOM

具体的这三个流程的代码的话可以自行查看,这里不在赘述。主要说一下 generate 函数

Untitled 6.png

函数中通过挂载到 vm上到 _c实例方法(其实就是调用了 createElement 方法)进行 ast生成的,然后render字符串通过 with()包裹返回staticRenderFns在之前已经做过静态标记,所以就直接取state里的staticRenderFns了。 ☠️

除了 _c 方法还有 _m 等方法通过 core/instance/render.js renderMixin函数调用 installRenderHelpers 函数,挂载到实例上

以上就是全部的compile阶段的整个流程,其中有部分代码(HTMLParse解析、optimize如何做静态标记、生成render字符串)没有深入探究,后续有机会再探讨! 🤣

Vue模版AST标记详情可参考

后记

既然我们通过 compile阶段 得到了render函数, runtime阶段那么Vue又是如何将render函数转换生成虚拟dom的呢? 😎

其实在 new Vue() 时Vue就调用了 initRender 方法,调用了一个 _c 方法也就是 createElement 方法(也就是在开发时写render函数的参数 h),这个方法返回了一个 _createElement 方法就会去递归创建 Vnode 树。具体代码在 core/vdom/index.js 中。

实例化流程

Untitled 7.png

initMixin方法中

Untitled 8.png

生成vdom后,vue就将对data等进行数据劫持等操作,以及其他的一些操作。至此,Vue就走到了created阶段,data和vdom也准备好了。

✍️中西结合疗效好

最后结合Vue的生命周期图来看,就能更好的理解 Vue的整个生命周期函数过程了

✋全文只代表作者个人观点,有分析错误的,记得指出哦~ 😀

Untitled 9.png