Vue 3 响应式全流程演示
{{ count }} 模板、依赖收集、微任务队列、mount/patch 更新真实 DOM
// ─── 日志工具 ───
var logEl = document.getElementById("log");
function log(msg) {
console.log(msg);
logEl.textContent = logEl.textContent + msg + "\n";
logEl.scrollTop = logEl.scrollHeight;
}
log("=== {{ count }} 响应式 + 真实 DOM ===\n");
// ─── 1. 依赖存储 ───
var targetMap = new WeakMap();
var activeEffect = null;
// ─── 2. 更新队列 ───
var queue = new Set();
var isFlushing = false;
// ─── 3. reactive ───
function reactive(obj) {
return new Proxy(obj, {
get: function (target, key) {
track(target, key);
return target[key];
},
set: function (target, key, value) {
var old = target[key];
target[key] = value;
if (old !== value) {
trigger(target, key);
}
return true;
}
});
}
function track(target, key) {
if (!activeEffect) {
return;
}
var depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
var dep = depsMap.get(key);
if (!dep) {
dep = new Set();
depsMap.set(key, dep);
}
dep.add(activeEffect);
log(" [track] " + String(key) + " ← effect#" + activeEffect.id);
}
function trigger(target, key) {
var depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
var dep = depsMap.get(key);
if (!dep) {
return;
}
log(" [trigger] " + String(key) + " 变了,通知 " + dep.size + " 个 effect");
dep.forEach(function (effectFn) {
if (effectFn.scheduler) {
effectFn.scheduler(effectFn);
} else {
effectFn.run();
}
});
}
function queueJob(job) {
if (queue.has(job)) {
log(" [queueJob] effect#" + job.id + " 已在队列,跳过");
return;
}
queue.add(job);
log(" [queueJob] effect#" + job.id + " 入队");
if (!isFlushing) {
isFlushing = true;
Promise.resolve().then(flushJobs);
}
}
function flushJobs() {
log("\n── 微任务:flushJobs ──");
queue.forEach(function (job) {
log(" [run] effect#" + job.id);
job.run();
});
queue.clear();
isFlushing = false;
log("── 微任务结束 ──\n");
}
// ─── 4. effect ───
var uid = 0;
function effect(fn, options) {
if (!options) {
options = {};
}
var effectFn = function () {
activeEffect = effectFn;
fn();
activeEffect = null;
};
effectFn.id = ++uid;
effectFn.run = effectFn;
effectFn.scheduler = options.scheduler;
effectFn();
return effectFn;
}
// ─── 5. 真实 DOM:mount / patch ───
var container = document.getElementById("app");
var state = reactive({ count: 0 });
var lastVNode = null;
// 模板 <div>{{ count }}</div> 编译后的 render
function render() {
var count = state.count; // 读 count → track
return { type: "div", text: String(count), el: null };
}
// 首次:创建真实 DOM 并插入页面
function mount(vnode, parent) {
var el = document.createElement(vnode.type);
el.textContent = vnode.text; // 真实 DOM API
vnode.el = el; // VNode 记住对应的真实节点
parent.appendChild(el);
log(" [patch] 挂载真实 DOM → createElement + textContent = " + vnode.text);
}
// 更新:只改 textContent,不重建整个 div
function patch(oldVNode, newVNode) {
if (oldVNode.text !== newVNode.text) {
newVNode.el = oldVNode.el; // 复用同一个 DOM 节点
newVNode.el.textContent = newVNode.text; // ★ 真实 DOM 在这里被更新
log(' [patch] 更新真实 DOM → textContent: "' + oldVNode.text + '" → "' + newVNode.text + '"');
}
}
function componentUpdateFn() {
log(" [render] 执行 render,读取 count...");
var newVNode = render();
if (!lastVNode) {
mount(newVNode, container);
} else {
patch(lastVNode, newVNode);
}
lastVNode = newVNode;
}
// ─── 6. 启动 ───
log("── 初始化 ──");
effect(componentUpdateFn, {
scheduler: function (effectFn) {
queueJob(effectFn);
}
});
// ─── 7. 按钮交互 ───
document.getElementById("btnAdd").onclick = function () {
log("\n── 点击 count++ ──");
state.count = state.count + 1;
};
document.getElementById("btnDemo").onclick = function () {
log("\n── 演示:同步连续 count=1,2,3 ──");
state.count = 1;
state.count = 2;
state.count = 3;
log("── 同步结束,DOM 可能还是旧值,等微任务... ──");
Promise.resolve().then(function () {
log("── 演示结束,DOM 应显示 3 ──\n");
});
};
document.getElementById("btnClear").onclick = function () {
logEl.textContent = "";
};componentUpdateFn 执行流程
render → diff → patch 的关键步骤与实例状态更新
console.log("=== componentUpdateFn 详细执行流程 ===");
// 模拟 Vue 组件实例
const instance = {
data: { count: 0, message: "Hello" },
isMounted: false,
subTree: null,
container: document.createElement("div"),
// 🎯 组件的 render 函数(就是 componentUpdateFn 的核心)
render() {
console.log(" 📝 render() 执行: 读取响应式数据");
// 这里会访问响应式数据,建立依赖关系
const count = this.data.count;
const message = this.data.message;
// 生成 Virtual DOM
return {
type: "div",
props: { class: "container" },
children: [
{ type: "h1", children: `Count: ${count}` },
{ type: "p", children: message },
{ type: "button", props: { onClick: () => this.data.count++ }, children: "Add" }
],
};
},
};
// 模拟 Diff 算法
function diff(oldVNode, newVNode) {
console.log(" 🔍 Diff 算法对比:");
const patches = [];
if (!oldVNode) {
patches.push({ type: "CREATE", vnode: newVNode });
console.log(" - 创建新节点");
return patches;
}
// 简单的属性对比
if (oldVNode.children !== newVNode.children) {
patches.push({ type: "UPDATE_TEXT", oldText: oldVNode.children, newText: newVNode.children });
console.log(` - 文本更新: "${oldVNode.children}" → "${newVNode.children}"`);
}
// 属性对比
const propPatches = diffProps(oldVNode.props, newVNode.props);
if (propPatches.length > 0) {
patches.push({ type: "UPDATE_PROPS", patches: propPatches });
}
return patches;
}
function diffProps(oldProps = {}, newProps = {}) {
const patches = [];
// 省略:属性级别的详细对比
return patches;
}
// 模拟 patch 函数
function patch(oldVNode, newVNode, container) {
console.log(" 🏗️ patch() 执行 DOM 操作");
if (!oldVNode) {
// 挂载阶段
console.log(" - 挂载新节点到 DOM");
mount(newVNode, container);
} else {
// 更新阶段
console.log(" - 更新现有 DOM 节点");
update(oldVNode, newVNode);
}
}
function mount(vnode, container) {
// 创建真实 DOM 的逻辑...
console.log(" - 创建元素:", vnode.type);
}
function update(oldVNode, newVNode) {
// 更新 DOM 的逻辑...
console.log(" - 最小化更新 DOM");
}
// 🎯 完整的 componentUpdateFn
const componentUpdateFn = () => {
console.group("🚀 componentUpdateFn 开始执行");
try {
// 1. 执行 render 函数生成新 VNode
console.log("1. 📝 调用 instance.render()");
const newVNode = instance.render.call(instance);
console.log(" 新 VNode:", JSON.stringify(newVNode, null, 2));
// 2. 执行 Diff 和 Patch
console.log("2. 🔄 执行 Diff & Patch");
const patches = diff(instance.subTree, newVNode);
console.log("3. 🏗️ 执行 DOM 更新");
patch(instance.subTree, newVNode, instance.container);
// 3. 更新实例状态
console.log("4. 📊 更新实例状态");
instance.subTree = newVNode;
if (!instance.isMounted) {
instance.isMounted = true;
console.log(" - 标记组件已挂载");
}
console.log("✅ componentUpdateFn 执行完成");
return newVNode;
} catch (error) {
console.error("❌ componentUpdateFn 执行出错:", error);
throw error;
} finally {
console.groupEnd();
}
};
// 测试
console.log("\n--- 第一次执行(挂载)---");
componentUpdateFn();
console.log("\n--- 数据变化后执行(更新)---");
instance.data.count = 5;
instance.data.message = "Updated!";
componentUpdateFn();