最近面试发现,使用Vue.js进行开发的团队似乎已经远超过了react了。因此学习总结了一下vue的观察者模式。Vue的响应式系统让开发者可以自如地实现数据与视图的同步。那么,Vue 是如何将这种响应式带到我们的应用中的呢?本文将深入探讨 Vue 中的 Watcher类,以及它如何与渲染函数和响应式系统相互作用,实现视图更新。
前置知识:观察者与依赖
在 Vue 中,响应式系统的核心是观察者(Watcher)和依赖(Dep)。简单来说,Watcher实例是一个观察者,它用于监视数据的变化,而Deps实例是一个依赖容器,用于存储与某个数据属性关联的一系列 Watcher实例。当数据发生变化时,Dep 会通知所有关联的 Watcher,从而触发回调函数并执行相应的操作(如更新视图)。
Vue 渲染函数
当我们在 Vue 中编写模板时,Vue 实际上会将这些模板编译成渲染函数。渲染函数是一个 JavaScript 函数,它会根据组件的数据生成相应的虚拟 DOM。在执行渲染函数的过程中,Vue 会访问我们在模板中绑定的数据属性。这些数据属性的访问触发了响应式系统的 getter,从而进行依赖收集。
举个例子,假设我们有以下模板:
<div id="app">
<p>{{ message }}</p>
</div>
Vue 会将此模板编译成如下渲染函数:
function render() {
with (this) {
return _c('div', { attrs: { id: 'app' } }, [
_c('p', [_v(_s(message))]),
]);
}
}
万物皆可 Watch:挂载时的响应式
在 Vue 组件实例创建过程中的挂载阶段,Vue 会创建一个渲染 Watcher 实例,负责监听整个组件的数据变化并重新渲染。当执行渲染函数时,Watcher实例会被设置为当前活动的 Watcher(Dep.target)。
随着渲染函数的执行,所有在模板中绑定的数据属性都会被访问。访问这些属性时,会触发响应式系统的 getter,从而将 Watcher实例 添加到相应属性的依赖(Dep)列表中。
让我们回顾一下 defineReactive 函数中的 getter:
get: function reactiveGetter() {
const dep = new Dep();
const value = getter ? getter.call(obj) : obj[key];
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
}
}
return value;
}
在执行渲染函数期间,访问数据属性的操作实际上建立了数据属性与 Watcher实例 之间的联系。这样,在数据属性发生变化时,这些 Watcher 实例会收到通知并重新计算渲染函数,从而实现视图的响应式更新。
触发更新:当数据变化时
当我们的数据属性发生变化时,响应式系统的 setter 会被触发。setter 会通知与该属性关联的依赖(Dep)列表中的所有 Watcher 实例。
在收到通知后,Watcher 会执行回调函数(在 mountComponent 函数中定义),重新计算渲染函数并生成新的虚拟 DOM。接下来,Vue 使用虚拟 DOM diffing 算法来比较新旧虚拟 DOM,并计算出需要进行的 DOM 更新。然后,Vue 将这些更新应用到实际的 DOM,从而实现视图的响应式更新。
让我们看一下简化版的 mountComponent 函数:
function mountComponent(vm, el) {
// 创建一个渲染 Watcher 实例
const updateComponent = () => {
vm._update(vm._render());
};
new Watcher(vm, updateComponent);
}
总结:响应式的魔法盒子
现在让我们回顾一下 Vue 的响应式奥秘是如何在幕后运作的:
- Vue 将模板编译成渲染函数。
- 在挂载阶段,Vue 为组件创建一个 Watcher 实例,监听整个组件的数据变化。
- 执行渲染函数时,访问模板中绑定的数据属性,从而触发响应式系统的 getter 并将 Watcher 添加到相应属性的依赖(Dep)列表中。
- 当数据属性发生变化,触发响应式系统的 setter,并通知所有关联的 Watcher。
- Watcher 收到通知,重新计算渲染函数并更新虚拟 DOM。
- Vue 使用虚拟 DOM diffing 算法计算 DOM 更新,然后将更新应用到实际的 DOM。
通过这个过程,Vue 能够实现数据与视图之间的响应式同步,为开发者带来更加便捷的开发体验。