# Vue.js 内部运行机制

Vue.js 内部运行整体流程图。

整体流程图

<div id="app">
  {{ message }}
</div>
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

# Vue 初始化

执行 new Vue() 之后。 Vue 内部会调用 _init 函数进行初始化,也就是上图的 init 过程。

Vue 初始化主要就干了几件事情,合并配置,初始化生命周期,初始化事件中心,初始化渲染,初始化 data、props、computed、watcher 等等。

其中最重要的是调用 initState 方法,通过 Object.defineProperty 设置 settergetter 函数,用来实现 「 响应式」 以及 「 依赖收集」

# Vue 实例挂载

初始化后,调用 vm.$mount(vm.$options.el) 实现实例的挂载。

Vue 实例挂载,核心是创建一个 渲染Watcher,进行组件初始化渲染。以及基于「 依赖收集」监听响应式数据变化,进行组件的渲染操作。

渲染Watcher,其中包括两个核心方法:vm._rendervm._update

# vm._render

vm._render 核心是,调用 compile 编译生成的 vm.$options.render 方法创建 VNode。其内部调用了createElement 方法,生成 VNode

# vm._update

vm._update 方法,会把 VNode 渲染成真实的 DOM

vm._update 方法被调用的时机有 2 个,一个是初始化渲染,一个是数据更新的时候。

# 编译

编译

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

# parse

parse 会用正则等方式解析 template 模板中的指令、classstyle 等属性,形成 AST

# optimize

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

# generate

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

在经历过 parseoptimizegenerate 这三个阶段以后,组件中挂载了渲染 VNode 所需的 render function 了。

# 响应式

响应式

Vue 初始化时,会调用 initState 方法,通过 Object.defineProperty 设置 settergetter 函数,用来实现 「 响应式」 以及 「 依赖收集」

组件渲染时,会调用 render function 生成 VNode,因此会读取 响应式数据 的值,所以会触发 getter 函数,通过劫持getter 函数可以进行「 依赖收集」

「 依赖收集」的目的是将 渲染Watcher 对象存放到当前闭包中的订阅者 Depsubs 数组中。

形成如下所示的这样一个关系(数据可以被多个组件共享)。

Dep

通过「 依赖收集」可以将组件中 响应式数据 的值以及属性 与 渲染Watcher 进行绑定。

当响应式数据的值更新时,会触发对应的 setter方法 ,setter 方法会通知之前「 依赖收集」得到的 Dep 中的每一个 Watcher,告诉它们自己的值改变了,需要重新渲染视图。这时候这些 Watcher 就会开始调用 update 来 更新视图,当然这中间还有一个 patch 的过程以及使用队列来异步更新的策略。

为了防止多次更改同一个属性(他们依赖的watcher相同),导致Depsubs 数组中存储重复的 渲染Watcher,造成重复更新渲染,订阅者 Dep需要进行去重操作。

为了防止多次修改同一个组件不同属性(他们依赖的watcher相同) 会导致频繁更新渲染,可以使用队列,实现异步更新,优化渲染。

注意:一个组件对应一个 渲染Watcher 对象。

# 更新视图

update

当响应式数据更新时,会通过 setter -> Watcher -> update 的流程来修改对应的视图,那么最终是如何更新视图的呢?

当数据变化后,执行 render function 就可以得到一个新的 VNode 节点,这个时候会调用 「vm.__patch__」了。我们会将新的 VNode 与旧的 VNode 一起传入 patch 进行比较,经过 diff 算法得出它们的「差异」。最后我们只需要将这些「差异」的对应 DOM 进行修改即可。

# 参考

更新时间: 6/14/2020, 4:26:01 AM