跳到主要内容

Watcher

Watcher的主要职责是观察某些数据的变化,当这些数据变化时执行相应的回调函数。这些数据通常是Vue实例的状态(如data、props等)或计算属性。Watcher在Vue的响应式系统中扮演着桥梁的角色,连接着数据(响应式属性)和视图(组件的渲染或计算属性的计算)。

核心功能

  1. 依赖收集:在组件的渲染过程中,会访问响应式数据,此时Watcher会自动将自己注册到这些数据的依赖列表(Dep实例)中,这个过程称为依赖收集。这确保了当数据变化时,Watcher可以被通知到。
  2. 派发更新:当依赖的响应式数据变化时,Dep实例会通知所有注册的Watcher执行更新操作。对于组件的渲染Watcher,这通常意味着重新渲染组件;对于计算属性的Watcher,则意味着重新计算属性的值。

Watcher的类型

  1. 渲染Watcher:每个组件都有一个渲染Watcher,用于其自身的更新和渲染。当组件中的响应式数据变化时,渲染Watcher会触发组件的重新渲染。
  2. 计算属性Watcher:每个计算属性背后都有一个Watcher实例,用于计算属性的惰性求值和缓存。计算属性Watcher仅在其依赖的响应式数据变化时才重新计算值。
  3. 侦听器Watcher:通过vm.$watch或组件选项中的watch属性创建的Watcher。它们用于执行数据变化时的自定义行为,如执行回调函数、命令式的数据监听等。

Watcher 实现

function defineReactive(obj, key) {
const dep = new Dep();
let value = obj[key];

Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
dep.depend(); // 收集依赖
return value;
},
set(newValue) {
if (value === newValue) return;
value = newValue;
dep.notify(); // 数据变化,通知依赖更新
}
});
}

class Watcher {
constructor(vm, expOrFn, callback) {
this.vm = vm;
this.getter = parsePath(expOrFn); // 解析表达式或函数
this.callback = callback;
this.value = this.get(); // 初始化时获取值,触发依赖收集
}

get() {
Dep.target = this; // 设置当前Watcher为全局的依赖目标
let value = this.getter.call(this.vm, this.vm); // 访问响应式数据,触发getter
Dep.target = null; // 清除全局的依赖目标
return value;
}

update() {
const oldValue = this.value;
this.value = this.get(); // 获取新值,触发依赖收集
this.callback.call(this.vm, this.value, oldValue); // 调用回调函数
}
}

Dep.target = this 之后,为什么又要置为 null

Dep.target = this; // 设置当前Watcher为全局的依赖目标
let value = this.getter.call(this.vm, this.vm); // 访问响应式数据,触发getter
Dep.target = null; // 清除全局的依赖目标

在Vue的响应式系统中,Dep.target被用来暂存当前正在评估的Watcher实例,这是一个全局的位置,用于依赖收集过程中暂时存储当前的Watcher。

设置Dep.target = this是为了让数据的getter知道有一个Watcher正在访问它,因此需要将该Watcher收集为依赖。

一旦当前的Watcher被添加到所有相关数据的依赖列表(Dep实例)中,Dep.target就应该被重置为null,原因包括:

  1. 防止重复收集:确保同一个Watcher不会被重复收集到同一个数据属性的依赖列表中。如果不将Dep.target置空,那么在Watcher的get方法执行期间访问任何响应式数据都会导致该Watcher被收集,这可能不是我们想要的结果。
  2. 避免无谓的依赖收集:在当前Watcher评估完成后,如果不清除Dep.target,后续的任何响应式数据访问(即使是不相关的数据访问)都会导致当前Watcher被错误地收集为依赖,这会引入不必要的依赖,增加计算和内存开销。
  3. 保持依赖收集的精确性:只有在Watcher的求值过程中访问的数据,才应该将该Watcher收集为依赖。完成求值后,清除Dep.target可以确保只有真正相关的数据变化会触发Watcher的更新。
  4. 支持嵌套组件和计算属性:Vue中常常会遇到嵌套组件和计算属性,这意味着在一个Watcher计算过程中可能会触发另一个Watcher的计算。将Dep.target置空允许Vue准确地管理和切换当前活跃的Watcher,确保依赖收集的正确性和组件更新的准确性。

总结

Watcher在Vue的响应式系统中扮演着中心角色,连接数据和视图。

通过依赖收集和派发更新, Watcher确保了当数据变化时,能够准确且高效地执行更新操作,无论是组件的渲染、计算属性的重新计算,还是用户自定义的监听行为。

Dep

在 Vue 的响应式系统中,Dep(依赖)是一个非常核心的概念,它用于管理某个数据(如组件的某个响应式属性)的所有订阅者(Watcher实例)。每个响应式数据(对象的属性或整个对象)都通过闭包关联一个Dep实例。Dep的主要职责是收集依赖(即订阅响应式数据的Watcher),并在响应式数据变化时通知这些依赖进行更新。

Dep 关键功能

  1. 依赖收集:当响应式数据被访问时,触发其getter函数,此时Dep负责收集当前激活的Watcher(正在执行的Watcher)。这意味着这个Watcher依赖于当前的数据。
  2. 派发更新:当响应式数据发生变化时(即触发setter),Dep实例会通知所有收集的Watcher执行更新操作。

Dep 实现

let uid = 0;

class Dep {
constructor() {
this.id = uid++;
this.subs = []; // 存储所有的Watcher依赖
}

addSub(sub) {
this.subs.push(sub); // 添加一个Watcher到依赖列表
}

removeSub(sub) {
const index = this.subs.indexOf(sub);
if (index > -1) {
this.subs.splice(index, 1); // 从依赖列表中移除一个Watcher
}
}

depend() {
if (Dep.target) {
Dep.target.addDep(this); // 如果当前有激活的Watcher,收集这个Watcher
}
}

notify() {
// 通知所有依赖(Watcher)进行更新
const subs = this.subs.slice();
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
}
}

// Dep.target 用于存储当前正在评估的Watcher
Dep.target = null;

Dep与Watcher的关系

  • 依赖收集时机:当一个Watcher被创建并执行其get方法以求值或执行函数时,会触发相关响应式数据的getter。此时,Dep.target被设置为当前的Watcher实例,Dep通过depend方法将这个Watcher收集为依赖。
  • 派发更新:当响应式数据变化(setter被调用)时,关联的Dep实例会调用notify方法,遍历所有收集的Watcher实例并调用它们的update方法,触发更新流程。

应用

  • 组件级别的更新:每个组件实例对应一个Watcher实例(渲染Watcher),它订阅了组件使用的所有响应式数据的Dep。当这些数据变化时,组件会重新渲染。
  • 计算属性和侦听器:计算属性和侦听器背后也各自有Watcher实例,它们订阅了各自依赖数据的Dep。当依赖数据变化,相关的计算属性会重新计算,侦听器会执行回调。