Vue 底层原理

Vue 响应式原理

Vue 最独特的特性之一,非侵入性的响应式系统。数据改变,视图会自动更新。

  • 当 Vue 实例的 data 初始化时,Vue 将遍历 data 对象所有属性,并使用 Object.defineProperty 把这些属性转为 getter/setter
  • 在 data 的属性被访问和修改时通知变化
  • 每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把 data 的属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。
  • 只有在 Vue 实例初始化时,就存在于 data 对象中的属性才能被主动转换为 getter/setter,这样属性才是响应的,如果需要动态添加属性,需要使用 Vue.set() 方法才能使得添加的属性是响应的
  • 数组的响应,主要是复写了数组的proto,将该属性赋值于一个 overrideArrayProto,这个是经过转换过 getter/setter 的,进行监听过的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function overrideArrayProto(array) {
var originalProto = Array.prototype,
overrideProto = Object.create(Array.prototype),
self = this,
result;
Object.keys(OAM).forEach(function (key, index, array) {
var method = OAM[index],
oldArray = [];
Object.defineProperty(overrideProto, method, {
value: function () {
oldArray = this.slice(0);
var args = [].slice.apply(arguments);
result = originalProto[method].apply(this, args);
self.observe(this); // 进行 getter/setter 转换,设置监听
self._callback(this, oldArray);
return result;
},
writable: true,
enumberable: true,
configurable: true
});
}, this);
array.__proto__ = overrideProto;
}

对象中存在的属性描述符主要有两种:数据描述符合存取描述符,数据描述符是一个具有值的属性;存取描述符是由 getter/setter 函数对描述的属性。存取描述符同时具有以下可选键值:
get:该方法返回值被用作属性值;set:该方法将接受唯一参数,并将该参数的新值分配给该属性。

异步更新队列

Vue 异步执行 DOM 更新,当数据变化时,Vue 将开启一个队列,并缓冲在同一事件循环中的所有数据改变,在下一个事件循环(tick)中,Vue 刷新队列并执行 DOM 更新,Vue 对异步队列尝试使用 Promie.then 和 MessageChannel,如果环境不支持,则使用 setTimeout(fn, 0)

Vue 中,如果想在数据变化后,立即执行某些任务,可以在 Vue.nextTick(callback) 方法的回调函数里设置。

Vue 的Virtual Dom

Virtual Dom(虚拟 DOM),是指通过 JavaScript 模拟创建一棵 DOM 对象树,当实际的 DOM 需要更新时,先会更新这棵由 JavaScript 模拟的 DOM 对象树,然后再批量更新实际的 DOM 树。这样实现的原因,主要是操作 JavaScript 对象要远远快于直接操作 DOM,同时也避免了多次重排或重绘,这样能够极大的优化性能。

diff 算法

Vue 监听到数据变化后,会自动更新视图。更新视图,在 Vue 内部会将 Virtual Dom 中的每一个 VNode 与之前一个旧的 VNode 对象进行 patch。

patch 就是将新老 VNode 节点进行比对,根据比较结果,最小程度的修改视图,减少实际的 DOM 操作。patch 的核心是 diff 算法。

diff算法是通过同层的树节点进行比较而非对树进行逐层搜索遍历的方式,所以时间复杂度只有O(n),是一种相当高效的算法。
也就是说比较只会在同层级进行, 不会跨层级比较。

节点的比较:

  • oldVNode === newVNode,该节点未发生改变
  • oldVNode.text !== newVNode.text,新旧节点存在 text,且 text 不相同,则会将实际的节点赋值为 newVNode.text
  • oldCh && ch && oldCh !== ch,两个节点都有子节点,并且不相同,则继续比较子节点
  • 只有新的节点有子节点,在实际 DOM 节点增加子节点
  • 新节点没有子节点,老节点有子节点,则实际DOM 节点删除子节点

子节点的比较,diff 的核心

diff 完整过程图

节点没有设置 key

节点设置了 key

节点设置 key 值后,能够让 Vue 尽可能复用节点。

参考:https://github.com/aooy/blog/issues/2

坚持原创技术分享,您的支持将鼓励我继续创作!