在 Vue.js 的响应式系统中,依赖收集和变化检测是核心机制,确保了数据的变动能够自动驱动视图更新。在上文我们已经了解了的依赖收集和变化检测与更新过程,主要依赖于 Watcher
、Dep
和响应式的 getter
和 setter
来实现。下面详细从底层代码解释这两个过程
文章目录
1. 依赖收集
依赖收集是指 Vue 在组件渲染过程中,收集那些与视图有关的数据属性,从而将它们与当前的 Watcher
建立关联。具体步骤如下:
-
激活
Watcher
:当一个组件开始渲染时,Vue 会创建一个Watcher
实例,这个Watcher
用来追踪组件模板中依赖的响应式数据。 -
触发
getter
进行依赖收集:- 在渲染组件时,Vue 会通过访问数据(如
data
或computed
)来更新 DOM。此时,响应式数据的getter
会被触发。 - 在
getter
中,Vue 会将当前活跃的Watcher
(即正在渲染的组件)添加到该数据属性的Dep
中,完成依赖收集。这意味着该数据与这个Watcher
建立了关联。
- 在渲染组件时,Vue 会通过访问数据(如
-
Dep
的作用:Dep
是一个依赖管理器,它管理与某个数据属性相关联的所有Watcher
。每个响应式属性都有一个Dep
,在getter
中会通过Dep
收集当前的Watcher
。- 核心目标:为每个被访问的数据属性记录哪些
Watcher
依赖于它。这样,当数据变化时,Vue 能通知与这个数据有关的所有Watcher
。
- 核心目标:为每个被访问的数据属性记录哪些
依赖收集的代码示例:
class Dep {
constructor() {
this.subs = []; // 订阅者列表,存储 Watcher
}
// 添加 Watcher 到订阅者列表
addSub(watcher) {
this.subs.push(watcher);
}
// 通知所有订阅者更新
notify() {
this.subs.forEach(sub => sub.update());
}
}
class Watcher {
constructor(updateFn) {
this.updateFn = updateFn;
// 记录当前的 Watcher
Dep.target = this;
}
// 更新视图
update() {
this.updateFn();
}
}
// 通过 getter 收集依赖
function defineReactive(obj, key, val) {
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
if (Dep.target) {
dep.addSub(Dep.target); // 依赖收集
}
return val;
},
set(newVal) {
if (newVal !== val) {
val = newVal;
dep.notify(); // 通知依赖更新
}
}
});
}
- Dep.target 是一个非常重要的全局变量,它用于依赖收集阶段,记录当前正在执行的 Watcher 实例。它的作用是在访问响应式数据时,能够将这个响应式数据的依赖(即当前的 Watcher)记录下来,便于后续数据变化时通知相关的 Watcher 进行更新。
2. 变化检测与更新
变化检测与更新发生在某个数据属性的值被修改时。这个过程确保数据的变更能驱动相应的视图更新。
-
触发
setter
:当你修改一个响应式数据属性时,setter
会被触发。在setter
中,Vue 通过Dep
来通知所有依赖这个属性的Watcher
,告诉它们数据发生了变化。 -
通知
Watcher
更新:- 每个依赖该属性的
Watcher
会通过Dep
的notify()
方法被通知。Dep
中存储了所有与该数据属性相关的Watcher
,它会调用每个Watcher
的update()
方法。
- 每个依赖该属性的
-
重新渲染视图:
- 当
update()
被调用时,Watcher
会执行其更新函数。这通常会重新执行与视图渲染相关的逻辑,最终触发组件的重新渲染,保证视图与数据保持同步。
- 当
变化检测与更新的代码示例:
// 修改响应式数据,触发 setter
data.message = 'Hello Vue.js';
// setter 中触发 notify,通知相关 Watcher 更新视图
function defineReactive(obj, key, val) {
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
if (Dep.target) {
dep.addSub(Dep.target); // 依赖收集
}
return val;
},
set(newVal) {
if (newVal !== val) {
val = newVal;
dep.notify(); // 数据变化时,通知 Watcher
}
}
});
}
// Watcher 更新方法
class Watcher {
constructor(updateFn) {
this.updateFn = updateFn;
Dep.target = this;
}
update() {
this.updateFn(); // 重新执行视图渲染
}
}
// 重新渲染视图的逻辑,假设渲染依赖 data.message
const renderWatcher = new Watcher(() => {
console.log(`视图更新: ${data.message}`);
});
// 修改数据,触发视图更新
data.message = 'Hello Vue.js 3';
3. 使用响应式原理来修改属性并更新实际的 DOM
<div id="app">
<p id="message">{{ message }}</p>
</div>
<button id="changeMessage">Change Message</button>
<script src="app.js"></script>
// Dep类,用于管理依赖
class Dep {
constructor() {
this.subs = [];
}
// 添加依赖
addSub(watcher) {
this.subs.push(watcher);
}
// 通知所有依赖,更新视图
notify() {
this.subs.forEach(sub => sub.update());
}
}
// 全局变量,记录当前的 Watcher
Dep.target = null;
// Watcher类,用于执行更新逻辑
class Watcher {
constructor(updateFn) {
this.updateFn = updateFn;
Dep.target = this; // 在实例化时记录当前的 Watcher
this.update(); // 初始化时调用更新函数
Dep.target = null; // 清空 Dep.target,防止后续误用
}
// 执行更新函数
update() {
this.updateFn();
}
}
// 响应式处理函数
function defineReactive(obj, key, val) {
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
if (Dep.target) {
dep.addSub(Dep.target); // 依赖收集
}
return val;
},
set(newVal) {
if (newVal !== val) {
val = newVal;
dep.notify(); // 通知依赖更新
}
}
});
}
// 创建响应式数据对象
const data = {};
defineReactive(data, 'message', 'Hello Vue.js');
// 获取 DOM 元素
const messageElement = document.getElementById('message');
const button = document.getElementById('changeMessage');
// Watcher,自动更新 DOM 元素
new Watcher(() => {
messageElement.innerText = data.message; // 更新视图中的文本
});
// 修改数据时触发视图更新
button.addEventListener('click', () => {
data.message = 'Hello World'; // 修改数据,自动更新 DOM
});
- 依赖收集:当 data.message 被访问时,getter 会把当前的 Watcher(通过 Dep.target 保存的)添加到 Dep 的依赖列表中。
- 变化检测与通知:当 data.message 被修改时,setter 会调用 dep.notify(),通知所有依赖于 message 的 Watcher。
- 自动更新 DOM:Watcher 的 update() 方法中,我们直接更新 DOM 中的 messageElement.innerText,这样在数据变化时,视图会自动更新。
总结
-
依赖收集:在组件渲染过程中,Vue 会通过访问响应式数据触发
getter
,并将当前正在渲染的Watcher
与数据属性建立依赖关系。Dep
是一个依赖管理器,负责管理该数据属性与哪些Watcher
相关。 -
变化检测与更新:当数据变化时,Vue 会触发数据属性的
setter
,通过Dep
通知所有依赖该属性的Watcher
。Watcher
会执行update()
方法,从而触发视图的重新渲染。
这个过程就是 Vue 响应式系统的核心机制,确保视图和数据之间的自动同步。
标签:Vue,target,Dep,视图,Watcher,响应,依赖,更新,底层 From: https://blog.csdn.net/2401_85770776/article/details/141994741