Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML ,所以能被遵循规范的浏览器和 HTML 解析器解析。在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。
模板渲染
Vue模板的渲染过程入下图所示,首先通过模板compiler编译器将模板编译成AST(抽象语法树),再由AST生成Vue的render函数,渲染函数结合数据再生成vnode(Virtual DOM树),对vnode进行diff和patch后生成新的UI。
Virtual DOM
由于操作真实的DOM会对性能带来损失,可以通过模拟真实的DOM对象树,建立一个虚拟DOM对真实DOM 发生的变化保持追踪。
vnode包含以下属性
createElement函数
createElement函数用来创建vnode,createElement接受的参数,
|
|
render函数
Vue的模板实际是编译成了render函数,可以使用Vue.compile(template)方法编译下面的模板
方法会返回一个对象,对象中有render和staticRenderFns两个值,下面是生成的render函数:
|
|
staticRenderFns数组与diff算法优化相关,我们会在编译阶段给后面不会发生变化的vnode节点打上static为true的标签,那些被标记为静态节点的vnode就会单独生成staticRenderFns函数:
观察者(Watcher)
每个Vue组件都有一个对应的Watcher,这个Watcher会在组件render的时候收集组件所依赖的数据,并在依赖有更新的时候重新渲染组件。
上图中我们可以以render函数作为分割线,render函数左边称为编译期,将vue的模板转换为渲染函数。render函数的右边是在vue的运行时,主要是基于渲染函数生成Virtual Dom树,然后对Virtual Dom进行diff和patch。
下面图描述了vue模板渲染的流程:
- new Vue():实例化Vue
- $mount():获取模板,并且在这过程中调用相关方法_count,new Watcher()实现数据响应式,当Watcher监听到数据变化,就会执行render函数输出一个新的vnode树形结构的数据
- compileToFunction():将tenplate编译成render函数。首先读缓存,在compileToFunction()中,会创建一个对象,把complie编译完后的对象的render 和 staticRenderFns 两个属性分别转换成函数缓存在对象中,然后把对象存进缓存,没有缓存就调用compile 方法拿到 render 函数的字符串形式,在通过new Function的方式生成真正的渲染函数。
- compile:将 template 编译成 render 函数的字符串形式,这个函数主要有三个步骤组成:parse,optimize和generate,最终输出一个包含 ast,render 和 staticRenderFns的对象。compile 函数主要是将 template 转换为 AST,优化 AST,再将 AST 转换为render函数字符串,render 函数与数据通过 Watcher 产生关联。
- update():update判断是否首次渲染,是则直接创建真实DOM,否则调用patch(),并且进行触发钩子和更新引用等其他操作。
- patch():新旧vnode对比的diff函数,对两个树结构进行完整的diff和patch的过程,最终只有发生了变化的节点才会被更新到真实 DOM 树上。
- destroy():完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。触发beforeDestroy和destroyed的钩子。在大多数场景中你不应该调用这个方法。最好使用 v-if 和 v-for 指令以数据驱动的方式控制子组件的生命周期。