编码母题 / P1 / 前端与专项架构 / 02
最小响应式状态管理器
先读清题意与边界,独立尝试,再展开思路并对照可运行的参考代码。
重要程度岗位专项难度困难预计完成50–70 分钟
题目是什么
实现最小 reactive/effect/computed:读取收集依赖,写入触发更新;条件分支切换时清理旧依赖,同步多次写入在微任务内去重。
输入输出示例
effect 从读取 a 切换为读取 b 后,继续写 a 不应再触发它。
练习起手式与验证
复制代码后补全空方法,再运行下方三组验证;复杂题所需的辅助类型与输入准备一并放在代码块中。
/*
* 题目:最小响应式状态管理器
* 要求:实现最小 reactive/effect/computed:读取收集依赖,写入触发更新;
* 条件分支切换时清理旧依赖,同步多次写入在微任务内去重。
* 示例:effect 从读取 a 切换为读取 b 后,继续写 a 不应再触发它。
*/
const targetMap = new WeakMap();
let activeEffect = null;
const queue = new Set();
let flushing = false;
function flushQueue() {}
function cleanup(runner) {}
function effect(fn, options = {}) {}
function track(target, key) {}
function trigger(target, key) {}
function reactive(target) {}
function computed(getter) {}
(async () => {
const state = reactive({ ok: true, a: 1, b: 2 });
let observed;
let runs = 0;
effect(() => {
runs += 1;
observed = state.ok ? state.a : state.b;
});
state.a = 3;
state.a = 4;
await Promise.resolve();
console.log(observed); // 预期:4
console.log(runs); // 预期:2
state.ok = false;
await Promise.resolve();
state.a = 9;
await Promise.resolve();
console.log(observed); // 预期:2
})().catch(console.error);
/*
* 题目:最小响应式状态管理器
* 要求:实现最小 reactive/effect/computed:读取收集依赖,写入触发更新;
* 条件分支切换时清理旧依赖,同步多次写入在微任务内去重。
* 示例:effect 从读取 a 切换为读取 b 后,继续写 a 不应再触发它。
*/
const targetMap = new WeakMap();
let activeEffect = null;
const queue = new Set();
let flushing = false;
function flushQueue() {}
function cleanup(runner) {}
function effect(fn, options = {}) {}
function track(target, key) {}
function trigger(target, key) {}
function reactive(target) {}
function computed(getter) {}
(async () => {
const state = reactive({ ok: true, a: 1, b: 2 });
let observed;
let runs = 0;
effect(() => {
runs += 1;
observed = state.ok ? state.a : state.b;
});
state.a = 3;
state.a = 4;
await Promise.resolve();
console.log(observed); // 预期:4
console.log(runs); // 预期:2
state.ok = false;
await Promise.resolve();
state.a = 9;
await Promise.resolve();
console.log(observed); // 预期:2
})().catch(console.error);解题思路
展开思路、不变量与常见边界
关键切入点:依赖图双向记录 target/key 与 effect;computed 懒计算并在依赖变化时标脏。
核心模型:WeakMap(target→Map(key→Set(effect)))。activeEffect 表示当前消费者; 每个 effect 反向保存 deps 便于清理。不变量:依赖边双向一致;写相同值不触发; 调度队列中同一 runner 最多一个。此练习不覆盖数组长度、Map/Set 和 effectScope。
参考代码与验证
“关键机制”注释用于解释实现,下载链接提供未经页面注释修改的原始 JS 文件。
const targetMap = new WeakMap();
let activeEffect = null;
const queue = new Set();
let flushing = false;
// 关键机制:依赖图双向记录 target/key 与 effect;computed 懒计算并在依赖变化时标脏。
function flushQueue() {
if (flushing) return;
flushing = true;
queueMicrotask(() => {
try {
for (const job of [...queue]) {
queue.delete(job);
job();
}
} finally {
flushing = false;
if (queue.size) flushQueue();
}
});
}
function cleanup(runner) {
for (const dep of runner.deps) dep.delete(runner);
runner.deps.length = 0;
}
function effect(fn, options = {}) {
const runner = () => {
cleanup(runner);
const previous = activeEffect;
activeEffect = runner;
try {
return fn();
} finally {
activeEffect = previous;
}
};
runner.deps = [];
runner.scheduler = options.scheduler;
if (!options.lazy) runner();
return runner;
}
function track(target, key) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) targetMap.set(target, (depsMap = new Map()));
let dep = depsMap.get(key);
if (!dep) depsMap.set(key, (dep = new Set()));
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
}
function trigger(target, key) {
const effects = new Set(targetMap.get(target)?.get(key) || []);
for (const runner of effects) {
if (runner.scheduler) runner.scheduler();
else {
queue.add(runner);
flushQueue();
}
}
}
function reactive(target) {
return new Proxy(target, {
get(object, key, receiver) {
const value = Reflect.get(object, key, receiver);
track(object, key);
return value && typeof value === "object" ? reactive(value) : value;
},
set(object, key, value, receiver) {
const old = object[key];
const changed = !Object.is(old, value);
const result = Reflect.set(object, key, value, receiver);
if (changed) trigger(object, key);
return result;
},
});
}
function computed(getter) {
let dirty = true;
let value;
const holder = {};
const runner = effect(getter, {
lazy: true,
scheduler: () => {
if (!dirty) {
dirty = true;
trigger(holder, "value");
}
},
});
return {
get value() {
track(holder, "value");
if (dirty) {
value = runner();
dirty = false;
}
return value;
},
};
}
// 三组以上可直接运行的验证;异步题使用独立执行块。
(async () => {
const state = reactive({ ok: true, a: 1, b: 2 });
let observed;
let runs = 0;
effect(() => {
runs += 1;
observed = state.ok ? state.a : state.b;
});
state.a = 3;
state.a = 4;
await Promise.resolve();
console.log(observed); // 预期:4
console.log(runs); // 预期:2
state.ok = false;
await Promise.resolve();
state.a = 9;
await Promise.resolve();
console.log(observed); // 预期:2
const doubled = computed(() => state.b * 2);
console.log(doubled.value); // 预期:4
state.b = 5;
console.log(doubled.value); // 预期:10
})().catch(console.error);
const targetMap = new WeakMap();
let activeEffect = null;
const queue = new Set();
let flushing = false;
// 关键机制:依赖图双向记录 target/key 与 effect;computed 懒计算并在依赖变化时标脏。
function flushQueue() {
if (flushing) return;
flushing = true;
queueMicrotask(() => {
try {
for (const job of [...queue]) {
queue.delete(job);
job();
}
} finally {
flushing = false;
if (queue.size) flushQueue();
}
});
}
function cleanup(runner) {
for (const dep of runner.deps) dep.delete(runner);
runner.deps.length = 0;
}
function effect(fn, options = {}) {
const runner = () => {
cleanup(runner);
const previous = activeEffect;
activeEffect = runner;
try {
return fn();
} finally {
activeEffect = previous;
}
};
runner.deps = [];
runner.scheduler = options.scheduler;
if (!options.lazy) runner();
return runner;
}
function track(target, key) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) targetMap.set(target, (depsMap = new Map()));
let dep = depsMap.get(key);
if (!dep) depsMap.set(key, (dep = new Set()));
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
}
function trigger(target, key) {
const effects = new Set(targetMap.get(target)?.get(key) || []);
for (const runner of effects) {
if (runner.scheduler) runner.scheduler();
else {
queue.add(runner);
flushQueue();
}
}
}
function reactive(target) {
return new Proxy(target, {
get(object, key, receiver) {
const value = Reflect.get(object, key, receiver);
track(object, key);
return value && typeof value === "object" ? reactive(value) : value;
},
set(object, key, value, receiver) {
const old = object[key];
const changed = !Object.is(old, value);
const result = Reflect.set(object, key, value, receiver);
if (changed) trigger(object, key);
return result;
},
});
}
function computed(getter) {
let dirty = true;
let value;
const holder = {};
const runner = effect(getter, {
lazy: true,
scheduler: () => {
if (!dirty) {
dirty = true;
trigger(holder, "value");
}
},
});
return {
get value() {
track(holder, "value");
if (dirty) {
value = runner();
dirty = false;
}
return value;
},
};
}
// 三组以上可直接运行的验证;异步题使用独立执行块。
(async () => {
const state = reactive({ ok: true, a: 1, b: 2 });
let observed;
let runs = 0;
effect(() => {
runs += 1;
observed = state.ok ? state.a : state.b;
});
state.a = 3;
state.a = 4;
await Promise.resolve();
console.log(observed); // 预期:4
console.log(runs); // 预期:2
state.ok = false;
await Promise.resolve();
state.a = 9;
await Promise.resolve();
console.log(observed); // 预期:2
const doubled = computed(() => state.b * 2);
console.log(doubled.value); // 预期:4
state.b = 5;
console.log(doubled.value); // 预期:10
})().catch(console.error);
复杂度分析
时间复杂度:追踪 O(1);触发 O(e)。单个依赖登记均摊常数;更新需遍历该属性的 e 个 effect。
空间复杂度:O(d)。依赖图存储 d 条属性与 effect 关联,队列至多容纳活跃 effect。