0%

Vue内部运行机制

对Vue.js进行一个概念梳理的过程。

全局概览

流程图如下:

image

初始化及挂载

image
new Vue()后,Vue会调用 _init 函数进行初始化,也就是这里的 init 过程。

会生成生命周期、事件、props、methods、data、computed 与 watch 等。
其中最重要的是通过 Object.defineProperty 设置 setter 和 getter函数,实现响应式依赖收集

初始化后调用$mount会挂载组件。
如果是运行时编译,即不存在 render function 但是存在template的情况,需要进行编译步骤。

编译 :

compile编译可分成 parse optimize generate 三个阶段,最终需要得到 render function

parse

会用正则等方式解析 template 模板中的指令、class、style登数据,形成AST

optimize

主要作用是标记 static 静态节点,这是 Vue 在编译过程中的一处优化,后面当 update 更新界面时,会有一个 patch 的过程, diff 算法会直接跳过静态节点,从而减少了比较的过程,优化了 patch 的性能。

generate

将AST 转化为 render function 字符串的过程,得到的结果是 render 字符串以及 staticRenderFns字符串

在经历过 parse optimize 与 generate 三个阶段后,组件中就会存在渲染VNode所需的 render function

响应式

xiangying

这里的 getter 和 setter 已经在上面介绍过,在 init 时候通过 Object.defineProperty 进行了绑定,它使得当被设置的对象被读取的时候会执行 getter 函数 ,而在当被赋值的时候会执行 setter 函数。

当 render funticon 被渲染的时候,因为会读取所需对象的值,所以会出发 getter 函数进行 依赖收集依赖收集的目的是将观察者Watcher对象存放到当前闭包中的订阅者Dep 的 subs 中。
形成如下关系:

image

在修改对象的值的时候,会触发 setter, setter通知之前 "依赖收集"得到的Dep中的每一个Watcher, 告诉他们自己的值改变了。

需要重新渲染视图,这时候这些 Watcher 就会开始调用 Update 来更新视图,当然这中间还有一个 patch 的过程以及使用队列来异步更新的策略

Virtual DOM

render function 会被转化为VNode节点。 Virtual DOM其实就是一颗 JS对象(VNode节点)作为基础的树,用对象属性来描述节点,实际上,实际上它是一层对真实DOM的抽象。

最终可以通过一系列操作使这颗树映射到真实环境上。

由于 Virtual DOM 是以 JavaScript 对象为基础而不依赖真实平台环境,所以使它具有了跨平台的能力,比如说浏览器平台、Weex、Node 等。

比如说下面这样一个例子:

1
2
3
4
5
6
7
8
9
{
tag: 'div', /*说明这是一个div标签*/
children: [ /*存放该标签的子节点*/
{
tag: 'a', /*说明这是一个a标签*/
text: 'click me' /*标签的内容*/
}
]
}

渲染后可以得到

1
2
3
<div>
<a>click me</a>
</div>

这只是一个简单的例子,实际上的节点有更多的属性来标志节点,比如 isStatic (代表是否为静态节点)、 isComment (代表是否为注释节点)等。

更新视图

image

在修改一个对象值的时候,会通过 setter > watcher > update 的流程来修改对应的视图,那么最终如何更新视图呢?

当数据变化后,执行 render function 就可以得到一个新的 VNode 节点,我们如果想要得到新的视图,最简单粗暴的方法就是直接解析这个新的 VNode 节点 , 然后用 innerHTML 直接全部渲染到真实DOM中。

但是!!!其实我们只对其中的一小块内容进行了修改,这样做有些浪费

patch 就只修改那些 改变了的地方
我们将新的VNode 与旧的 VNode 一起传入 进行patch比较,经过diff算法 得出他们的 差异,最后我们只需要将这些差异的对应DOM进行修改即可。