核心概念
响应式系统的核心是建立数据与副作用函数之间的关联关系,当数据变化时自动触发相关副作用函数。
系统架构
🔧 核心数据结构
let activeEffect = null; // 当前激活的副作用函数
const depsMap = new Map(); // 依赖收集映射表
const effectStack = []; // 副作用函数栈(解决嵌套问题)
// depsMap 结构示例:
// Map(3) {
// 'a' => Set(2) { [Function: environment], [Function: environment] },
// 'b' => Set(1) { [Function: environment] },
// 'c' => Set(1) { [Function: environment] }
// }🎯 响应式数据
const data = { a: 1, b: 2, c: 3 };
const state = new Proxy(data, {
get(target, key) {
track(target, key); // 📥 依赖收集
return target[key];
},
set(target, key, value) {
target[key] = value;
trigger(target, key); // 📤 派发更新
return true;
}
});核心实现
1. 📥 依赖收集 (Track)
const track = (target, key) => {
if (activeEffect) {
let deps = depsMap.get(key); // 获取属性对应的依赖集合
if (!deps) {
deps = new Set(); // 🆕 创建新的依赖集合
depsMap.set(key, deps);
}
activeEffect.deps.push(deps); // 🔗 记录当前环境函数的所有依赖集合
deps.add(activeEffect); // ➕ 将环境函数加入依赖集合
}
};2. 📤 派发更新 (Trigger)
const trigger = (target, key) => {
const deps = depsMap.get(key); // 获取依赖集合
if (deps) {
// ☑️ 关键:创建快照避免无限循环
const effectToRun = new Set(deps);
effectToRun.forEach(effect => effect());
}
};3. 🎭 副作用函数 (Effect)
function effect(fn) {
// 🎪 创建环境函数(形成闭包)
const environment = () => {
activeEffect = environment; // 🎯 激活当前环境函数
effectStack.push(environment); // ⬆️ 入栈
clearDeps(environment); // 🧹 清理旧依赖
fn(); // 🚀 执行副作用函数
activeEffect = effectStack.pop(); // ⬇️ 出栈
};
environment.deps = []; // 📝 初始化依赖记录
environment(); // 🎬 立即执行
}4. 🧹 依赖清理 (clearDeps)
function clearDeps(environment) {
const deps = environment.deps; // 📦 获取所有依赖集合
if (deps.length) {
deps.forEach(dep => {
dep.delete(environment); // 🗑️ 从每个依赖集合中删除自己
if (dep.size === 0) { // 🎯 如果集合为空则完全清理
for (let [key, value] of depsMap) {
if (value === dep) {
depsMap.delete(key); // 🧽 删除空依赖集合
}
}
}
});
deps.length = 0; // 🔄 清空依赖记录
}
}系统工作原理
📊 依赖关系建立过程
初始状态: activeEffect = null, depsMap = {}
effect(() => { state.a }) 执行:
1. 🎪 创建 environment 环境函数
2. 🎯 activeEffect = environment
3. 📥 访问 state.a → 触发 track
4. ➕ 将 environment 加入 depsMap['a'] 集合
5. 🔗 environment.deps 记录 [depsMap['a']]
最终: depsMap = { 'a': Set([environment]) }🔄 动态依赖更新
effect(() => {
if (state.a === 1) {
state.b; // ✅ 条件成立时建立 b 依赖
} else {
state.c; // ✅ 条件不成立时建立 c 依赖
}
});执行过程:
🧹 每次执行前先清理旧依赖
📥 根据条件动态建立新依赖
🔄 确保依赖关系始终准确
设计演进历程
第1版:基础实现
let activeEffect = null;
const depsMap = new Map();
function track(target, key) {
if (activeEffect) {
let deps = depsMap.get(key);
if (!deps) {
deps = new Set();
depsMap.set(key, deps);
}
deps.add(activeEffect);
}
}
function trigger(target, key) {
const deps = depsMap.get(key);
if (deps) {
deps.forEach(effect => effect());
}
}
function effect(fn) {
activeEffect = fn;
fn();
activeEffect = null;
}架构图示:
activeEffect 作用:
⚠️ 问题一:动态依赖失效
测试用例:
state.b; // 第一次:建立 b 依赖
} else {
state.c; // 第二次:应该建立 c 依赖
}
console.log("执行了函数");
});问题分析:
第一次执行:建立
a → Set(fn)、b → Set(fn)依赖修改
state.a = 100后重新执行但
activeEffect = null,无法进入依赖收集
依赖关系变化:
// 第一次执行结果:
Map(1) { 'a' => Set(1) { [Function (anonymous)] } }
Map(2) {
'a' => Set(1) { [Function (anonymous)] },
'b' => Set(1) { [Function (anonymous)] }
}
执行了函数
// 期望第二次执行结果:
Map(2) {
'a' => Set(1) { [Function (anonymous)] },
'c' => Set(1) { [Function (anonymous)] }
}问题根源图示:
✅ 解决方案:环境函数包装
function effect(fn) {
const environment = () => {
activeEffect = environment; // 🎯 关键:重新激活
fn();
activeEffect = null;
};
environment();
}改进效果图示:
⚠️ 问题二:旧依赖残留
问题描述: 条件分支变化时,旧的依赖关系没有清理,导致依赖关系不准确。
✅ 解决方案:依赖清理机制
function cleanup(environment) {
let deps = environment.deps;
if (deps.length) {
deps.forEach(dep => {
dep.delete(environment);
if (dep.size === 0) {
for (let [key, value] of depsMap) {
if (value === dep) {
depsMap.delete(key);
}
}
}
});
deps.length = 0;
}
}清理机制图示:

⚠️ 问题三:无限循环
产生场景:
effect(() => {
if (state.a === 1) {
state.b;
} else {
state.c;
}
console.log("执行了函数1");
});
effect(() => {
console.log(state.a);
console.log(state.c);
console.log("执行了函数2");
});
state.a = 2; // 🔄 触发无限循环循环过程:
📤
state.a = 2触发 trigger🚀 执行第二个 effect 函数
📥 访问
state.a重新收集依赖🔄 再次触发 trigger,形成循环
✅ 解决方案:快照机制
function trigger(target, key) {
const deps = depsMap.get(key);
if (deps) {
const effectsToRun = new Set(deps); // ☑️ 创建快照
effectsToRun.forEach(effect => effect());
}
}⚠️ 问题四:嵌套 Effect 异常
测试用例:
effect(() => {
effect(() => {
state.a;
console.log("执行了函数2");
});
state.b;
console.log("执行了函数1");
});异常输出:
Map(1) { 'a' => Set(1) { [Function: environment] { deps: [Array] } } }
执行了函数2
Map(1) { 'a' => Set(1) { [Function: environment] { deps: [Array] } } }
执行了函数1 // ❌ 缺少 b 属性的依赖问题根源图示:
✅ 解决方案:函数栈机制
function effect(fn) {
const environment = () => {
activeEffect = environment;
effectStack.push(environment); // ⬆️ 入栈
clearDeps(environment);
fn();
effectStack.pop(); // ⬇️ 出栈
activeEffect = effectStack[effectStack.length - 1]; // 恢复上一个
};
environment.deps = [];
environment();
}关键技术点
1. 🎯 环境函数设计
// ❌ 错误做法:直接使用原函数
activeEffect = fn;
// ✅ 正确做法:包装环境函数
const environment = () => {
activeEffect = environment; // 🎯 保持引用一致性
// ... 其他逻辑
};2. 🧹 依赖清理的重要性
问题场景:
effect(() => {
if (state.a === 1) {
state.b; // 第一次:建立 b 依赖
} else {
state.c; // 第二次:应该建立 c 依赖
}
});解决方案:
🧹 每次执行前清理旧依赖
📥 重新建立准确的新依赖关系
3. 🎪 嵌套 Effect 处理
effect(() => {
effect(() => {
state.a;
console.log("内层函数");
});
state.b;
console.log("外层函数");
});栈机制保证:
⬆️ 内层 effect 入栈时不破坏外层 activeEffect
⬇️ 内层执行完毕正确恢复外层上下文
4. 🔄 避免无限循环
问题代码:
// ❌ 直接遍历会导致无限循环
deps.forEach(effect => effect());解决方案:
// ✅ 创建快照避免动态修改影响
const effectToRun = new Set(deps);
effectToRun.forEach(effect => effect());完整示例
// 使用示例
effect(() => {
effect(() => {
effect(() => {
state.c;
console.log("执行函数3");
});
state.a;
console.log("执行函数2");
});
state.b;
console.log("执行函数1");
});完整代码
原创
响应式系统原理与实现
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。




评论交流
欢迎留下你的想法