一、编译流程
1、为什么需要编译流程
无论是在脚手架项目中还是在单文件html中,只要使用Vue框架开发,我们就可以书写出各种好用的指令,比如v-for、v-model、@event
…等等,但是浏览器默认情况下是只认识原生js、css
以及html
的呀,为什么我们的Vue应用程序可以在浏览器上跑呢?那是以因为当我们在书写出一个Vue的模板的时候,Vue会在内部做一个编译的过程,这个过程就是把我们写的浏览器不认识的带有指令版本的模板(注意这里模板不是html
),经过一次编译,把它变成浏览器能认识的内容。
2、编译流程过程
实际上Vue最终在渲染函数的时候是通过js进行渲染的,Vue封装了所有创建DOM的操作,基于虚拟dom来渲染真实dom(真实dom包含太多无用属性,操作起来太消耗性能),因此创建一个虚拟dom对这个虚拟dom进行操作,最后生成一个最终的虚拟dom,然后利用js创建dom的方法生成真实dom。在源码patform/web/runtime/node-opts
中可以找到所有创建DOM的方法,而这些方法最终会被引用用于创建真正的DOM。源码如下:
而虚拟dom实际上是一个对象,包含一个元素的很多属性、特征等,那么虚拟dom如何生成呢,这就要印除Vue的渲染函数了,即render函数,这个函数用来生成虚拟dom,而这个渲染函数是通过抽象语法树AST使用字符串拼接出来的,而抽象语法树是通过模板解析出来的。
vue编译流程入如下
二、核心函数
在上一章响应式原理中,我们提到了这个问题。我们说当触发响应式数据的改变的时候,会触发getter执行,getter执行时就会循环遍历属于自己Dep对象中的watcher,然后依次执行他们的update方法,还有印象吧!
然后我们接下看一下在update方法中做了什么!记得打开源码
core/observer/watcher中查看一下!
最终会执行run方法,这个这中间会去执行queueWatcher这个方法,这个方法是一个队列结构的任务调度器,是一个异步操作,这个异步操作里面实际上也执行的是run方法,然后run方法执行的实际上是get方法。
这里可以看到首先就会执行value = this.getter.call(vm,vm)
这个就是核心了。我们看一下这个this.getter是个什么函数。
这其实是一个路径表达式或者是一个渲染函数。我们来看一下渲染函数长什么样子。
这就是渲染函数的真相了,
在这其中,update接受的是一个虚拟DOM,从而我们可以得出我之前的结论,渲染函数执行的结果是一个虚拟DOM,虚拟DOM在这个里面就会与之前的旧DOM进行__patch_,实际上这里就是进行diff啦,包括实际上的更新和渲染真实DOM,但这不是我们今天这篇博客探讨的重点,我们继续来看那个_render是怎么回事。它返回的是一个虚拟DOM,因此它一定会生成一个虚拟DOM,
而实际上的render渲染函数是通过这个compileToFunctions
函数得来的,将渲染函数挂载到了options配置项身上,供_render调用,我们来看一下,这个compileToFunctions调用后生成了什么。
就是一串这样的东西,实际上是一个函数被字符串化的一个函数字符串,因为vue这编译过程中,这个函数是根据抽象语法树,用字符串拼接出来的。仔细看一下,里面_s()中的东西就是那些动态的数据了,在这个渲染函数被生成时,这个响应式数据是没有被访问到的,只有调用这个函数,生成虚拟DOM时,响应式数据才会被访问到,真正的值会被替换到虚拟DOM中。
这是我打印出来的虚拟DOM,在虚拟DOM中所有的循环,响应式变量等vue操作符都已经生效,真正的值都已经被替换到了虚拟DOM中,可以这么说,虚拟DOM,和真实DOM,除了更小一点,几乎就是一一对应的了。
那么接下来我们就来研究一下这个渲染函数是怎么得来的吧,这也是最难啃的了。
1.模板 to ast
我们直接打开vue.js生产环境的源码看吧,因为那个开发环境的源码文件太多找半天不容易看懂,因为大部分结结构我们其实已经知道了,直接在这个文件中搜索parse( 就会找到这个函数,他会把模板字符串,也就是我们写的vue源代码,传进去,生成抽象语法树,并对其进行优化处理,然后利用抽象语法树生成渲染函数后再将抽象语法树和渲染函数都返回出去。
由 | 到 | by |
---|---|---|
模板字符串(template) | ast抽象语法树 | parse() |
ast抽象语法树 | 渲染函数 | generate |
所以我们今天的目的就是把parse()
和generate()
这两个函数给弄明白。
因为源码中这个parse方法实在是太大了,因此我画了一个流程图,一起来看一下吧!