广瀚云办公
55.99MB · 2025-10-09
Vue 的数据响应式,是当数据变化时,自动执行依赖于它的副作用函数。
Reactive
响应式首先需要一个依赖追踪系统,用于依赖追踪,并在数据变化时通知相关的副作用函数重新执行。
class Dep {
constructor() {
// 用 Set 来存储订阅的副作用函数,避免重复添加
this.subscribers = new Set();
}
// 添加依赖
depend() {
// activeEffect 是当前正在执行的副作用函数
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
// 通知所有依赖更新
notify() {
this.subscribers.forEach((effect) => effect.update());
}
}
activeEffect
是一个全局变量,用于存储被注册的副作用函数,在下文的执行 effect
函数执行前赋值。
Watcher
实现 用于管理副作用函数的执行,并在数据变化时重新执行这些函数
// activeEffect 用于存储当前正在执行的副作用函数
let activeEffect = null;
class Watcher {
constructor(effect) {
this.effect = effect;
this.run(); // 初始化时执行一次副作用函数
}
// 执行副作用函数
run() {
activeEffect = this;
this.effect();
activeEffect = null;
}
// 数据变化时调用,重新执行副作用函数
update() {
this.run();
}
}
// 定义一个 effect 函数,用于注册副作用函数
function effect(fn) {
new Watcher(fn);
}
effect
注册副作用函数,通过实例化 Watcher
,将改函数赋值给全局变量 activeEffect
,effect
函数的定义,可以不在通过硬编码的格式定义副作用函数,即使是一个匿名函数,也能通过全局变量 activeEffect
来找到并执行。
响应式数据实现 使用 Proxy
来拦截对对象属性的访问(get
)和修改(set
),从而实现响应式数据。
// 实现一个reactive函数,将一个普通对象转换为响应式对象
// 使用 WeakMap 来存储对象及其属性对应的 Dep
const targetMap = new WeakMap();
// 获取对象属性对应的 Dep
function getDep(target, key) {
let depsMap = targetMap.get(target);
// 如果没有对应的依赖 Map,则创建一个新的 Map
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
// 如果没有对应的 Dep,则创建一个新的 Dep
if (!dep) {
dep = new Dep();
depsMap.set(key, dep);
}
return dep;
}
// 创建响应式对象
function reactive(target) {
const handler = {
get(target, key, receiver) {
// 拿到对应的 Dep
const dep = getDep(target, key);
dep.depend(); // 追踪依赖
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
const dep = getDep(target, key);
dep.notify(); // 通知依赖更新
return result;
},
};
return new Proxy(target, handler);
}
从上述代码可以看出构造数据结构的方式,涉及到了三种数据结构:WeakMap
,Map
,Set
。
WeakMap
由 target --> Map
构成;Map
由 key --> Set
构成;其中 WeakMap
的键是原始对象 target
,WeakMap
的值是一个 Map
实例,而 Map
的键是原始对象 target
中的 key
,Map
的值是一个由副作用函数组成的 Set
。
为什么要使用 WeakMap
来创建?
为什么要使用 Reflect
?
更新 dom
视图,需要一个 render
函数来更新视图,当响应式数据发生变化时,自动调用 render
函数更新视图
<div id="app">
<div id="count-display"></div>
<button id="increment-btn">Increment</button>
</div>
function render() {
document.getElementById(
"count-display"
).innerText = `Count: ${state.count}`;
}
综合应用
const state = reactive({
count: 0,
});
// 默认先触发一次,初始化渲染
effect(() => {
render();
});
// 绑定点击事件,点击按钮时,触发了render方法,页面的数据自动变化
document.getElementById("increment-btn").addEventListener("click", () => {
state.count++;
});