Vue是如何收集依赖的?
在 Vue 中,依赖收集是一个重要的概念,它是 Vue 实现响应式数据的核心。当一个组件被初始化时,Vue 会对组件的 data 进行初始化,将普通的 JavaScript 对象变成响应式对象。在这个过程中,Vue 会进行依赖收集,以便在数据发生变化时,能够通知到所有依赖这个数据的地方。
依赖收集的过程主要涉及到三个类:Dep
、Watcher
和 Vue
。
1. Dep
类
Dep
是依赖收集的核心,它的主要作用是管理所有的 Watcher
。Dep
类中有一个静态属性 target
,它指向当前正在计算的 Watcher
,保证了同一时间全局只有一个 Watcher
被计算。Dep
类中还有一个 subs
属性,它是一个 Watcher
的数组,用来存储所有依赖这个 Dep
的 Watcher
。
1. 静态属性 target
Dep
类中有一个静态属性 target
。这个属性的作用是指向当前正在计算的 Watcher
,它的存在保证了在同一时间内全局只有一个 Watcher
被计算。你可以把它想象成一个焦点,告诉系统当前需要关注哪个 Watcher
。
2. subs 属性
另一个属性是 subs
,它是一个存储 Watcher
的数组。这意味着 Dep
类实际上就是对 Watcher
的管理者。当数据发生变化时,Dep
就会通知所有依赖于它的 Watcher
进行更新,而这些 Watcher
则会负责相应的视图更新。
3. 方法:addSub 和 removeSub
addSub
方法用于将 Watcher
添加到 subs
数组中,而 removeSub
方法则是用来从 subs
数组中移除对应的 Watcher
。
4. 方法:depend 和 notify
-
depend
方法的作用是在计算属性或者渲染过程中建立依赖关系。如果当前存在正在计算的Watcher
(即Dep.target
存在),那么就会将当前的Dep
与该Watcher
建立关联。 -
notify
方法则是在数据发生变化时通知所有依赖这个Dep
的Watcher
执行更新操作。它遍历subs
数组中的所有Watcher
,并逐一触发它们的更新方法。
在实际工作项目中,Dep
类的概念对于理解 Vue.js 的响应式原理和数据双向绑定机制至关重要。在大型前端应用中,了解依赖收集的过程以及如何管理依赖关系是非常有益的,尤其是在构建自定义组件或处理复杂的数据流时。
案例需求
假设有一个用户界面,其中包含一个数字输入框和一个显示框。输入框中的值会影响显示框中的内容。我们希望在输入框的值改变时,显示框能够自动更新。
class Dep {
constructor() {
this.subs = []; // 存储所有依赖这个Dep的Watcher
}
depend() {
if (Dep.target) {
Dep.target.addDep(this); // 将当前Dep与正在计算的Watcher关联起来
}
}
notify() {
this.subs.forEach(watcher => {
watcher.update(); // 通知所有依赖这个Dep的Watcher进行更新
});
}
}
Dep.target = null; // 全局的当前正在计算的Watcher
class Watcher {
constructor() {
Dep.target = this;
}
addDep(dep) {
dep.subs.push(this); // 将当前Watcher加入到Dep的subs数组中
}
update() {
// 执行更新操作
}
}
// 示例用法
let dep = new Dep();
let watcher = new Watcher();
dep.depend(); // 将当前Dep与正在计算的Watcher关联起来
dep.notify(); // 通知所有依赖这个Dep的Watcher进行更新
- 创建Dep类,其中包括subs数组和depend、notify方法。
- 创建Watcher类,其中包括addDep和update方法。
- 在Dep类中,通过target属性指向当前正在计算的Watcher,在depend方法中将当前Dep与正在计算的Watcher关联起来,在notify方法中通知所有依赖这个Dep的Watcher进行更新。
- 在Watcher类中,通过addDep方法将当前Watcher加入到Dep的subs数组中,在update方法中执行更新操作。
- Dep类和Watcher类是数据响应式系统的核心,理解其原理对于理解Vue等前端框架的工作原理非常重要。
- 在实际项目中,可以根据具体需求对Dep类和Watcher类进行定制和扩展,以满足不同的业务场景需求。
案例需求
假设我们有一个需求,希望在用户修改某个输入框的数值时,页面上的其他相关部分能够实时更新。这就需要利用 Vue.js 的响应式系统来实现数据的自动更新。在这种情况下,理解 Dep
类的作用将有助于我们更好地设计数据流和组件通信。
<template>
<div>
<input v-model="value" @input="updateOtherSections" />
<div>{{ calculatedValue }}</div>
</div>
</template>
<script>
export default {
data() {
return {
value: 0,
calculatedValue: 0
};
},
mounted() {
this.$watch(
() => this.value,
() => {
this.calculatedValue = this.value * 2; // 示例:当输入框数值改变时触发更新
}
);
},
methods: {
updateOtherSections() {
// 更新其他相关部分的逻辑
}
}
};
</script>
- 在
mounted
钩子中,通过this.$watch
监听value
的变化。 - 当
value
变化时,会触发相应的更新函数,计算新的值并更新到calculatedValue
。 - 用户输入框的值变化会引起
value
的变化,从而触发calculatedValue
的更新。
总结
Dep
类通过管理依赖关系,用于管理所有的Watcher,确保了数据变化时相关的视图能够得到更新。它利用 subs
数组存储依赖它的 Watcher
,并通过 notify
方法通知它们进行更新。同时,使用静态属性 target
确保了在同一时间内只有一个全局的 Watcher
被计算。通过理解 Dep
类的作用,我们可以更好地设计和构建具有复杂数据流的 Vue 组件,并更好地应对实际项目中的需求。
2. Watcher
类
Watcher
是一个用来计算表达式的类。在 Watcher
的构造函数中,它会执行表达式,这个表达式可能会触发数据的 getter
,从而进行依赖收集。Watcher
类中还有一个 addDep
方法,它会将当前的 Watcher
添加到 Dep
的 subs
数组中。
class Watcher {
getter;
...
constructor (vm, expression){
...
this.getter = expression;
this.get();
}
get () {
pushTarget(this);
value = this.getter.call(vm, vm)
...
return value
}
addDep (dep){
...
dep.addSub(this)
}
...
}
function pushTarget (_target) {
Dep.target = _target
}
构造函数
Watcher
类有一个构造函数,它接受两个参数:vm
和 expression
。在构造函数中,将传入的 expression
赋值给 getter
属性,并立即调用 get
方法。
get 方法
-
get
方法是Watcher
类中最关键的方法之一。当Watcher
被创建时,会立即调用get
方法。 - 在
get
方法内部,会将当前的Watcher
推入一个全局的位置(通过pushTarget
函数),然后执行expression
,这个expression
可能是一个函数或者计算属性的表达式。 - 在执行过程中,会触发对应数据的
getter
,从而建立起依赖关系,并且触发了依赖收集过程。
addDep 方法
-
addDep
方法用于将当前的Watcher
添加到指定的Dep
实例中,建立起Watcher
和Dep
之间的关联。 - 这样,在数据变化时,
Dep
就能够通知到相应的Watcher
进行更新操作。
pushTarget 函数
pushTarget
函数的作用是将当前的 Watcher
设置为全局唯一的 Dep.target
,以确保在同一时间内全局只有一个 Watcher
被计算。
- 在
get
方法中,通过调用expression
来触发数据的getter
,从而建立起依赖关系。- 使用
addDep
方法将当前的Watcher
添加到对应的Dep
实例中,确保在数据变化时能够及时通知到相关的Watcher
进行更新操作。
总结
Watcher
类通过 get
方法建立了依赖关系,并且在初始化时会立即执行 expression
,从而触发对应数据的 getter
,进而进行依赖收集。通过 addDep
方法,Watcher
和 Dep
建立了关联,确保了数据变化时能够及时通知到相关的 Watcher
进行更新操作。
3. Vue
类
Vue
类是 Vue 的入口,它的主要作用是初始化 Vue 应用。在 Vue
类的初始化过程中,会对组件的 data 进行初始化,将普通的 JavaScript 对象变成响应式对象。在这个过程中,会进行依赖收集。
依赖收集的过程如下:
-
首先,
Vue
会对组件的 data 进行初始化,将普通的 JavaScript 对象变成响应式对象。 -
然后,
Vue
会实例化一个Watcher
,并执行Watcher
的get
方法。 -
在
get
方法中,Watcher
会执行表达式,这个表达式可能会触发数据的getter
,从而进行依赖收集。 -
在
getter
中,会调用Dep.target.addDep(this)
,将当前的Watcher
添加到数据的Dep
的subs
数组中。 -
这样,当数据发生变化时,
Dep
就可以通过subs
数组,找到所有依赖这个数据的Watcher
,并通知它们数据发生了变化。
这就是 Vue 如何进行依赖收集的过程。
update***ponent = () => {
vm._update(vm._render())
}
new Watcher(vm, update***ponent)
update***ponent 函数
update***ponent
函数是一个箭头函数,它的作用是更新 Vue 实例的视图。在这个函数内部,它调用了 vm._render()
方法生成虚拟 DOM,并将其传递给 vm._update()
方法来更新实际的 DOM。
创建 Watcher
通过 new Watcher(vm, update***ponent)
创建了一个新的 Watcher
实例。这里的 vm
是 Vue 实例,update***ponent
是上面定义的更新函数。
在创建 Watcher
实例时,会立即执行 update***ponent
函数,这样就建立了依赖关系,update***ponent
函数中所使用的数据发生变化时,对应的 Watcher
就能够收到通知,并触发更新操作。
- 当创建
Watcher
实例时,会立即执行update***ponent
函数,从而进行依赖收集并建立起对应的关联。update***ponent
函数的执行将触发 Vue 实例的重新渲染过程,确保视图能够及时地响应数据的变化。
总结
通过创建 Watcher
实例,将 update***ponent
函数与 Vue 实例建立了关联,确保了当相关数据发生变化时,能够触发视图的更新操作。这种机制保证了 Vue 的响应式系统能够自动追踪数据的变化,并及时地更新视图,从而实现了数据驱动视图的效果。
持续学习总结记录中,回顾一下上面的内容:
Vue通过在数据属性的getter中收集依赖。当一个数据被访问时,Watcher会被添加到依赖列表中。这样,当数据变化时,Watcher就能够得到通知,并进行相应的更新操作,从而确保视图能够及时地反映数据的变化。