|
<!DOCTYPE html>
|
<html lang="en">
|
<head>
|
<meta charset="UTF-8">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<title>Vue3响应式系统原理 · 宋体五号示意图</title>
|
<style>
|
/* 全局字体设置:宋体五号 (五号 ≈ 10.5pt ≈ 14px,为屏幕清晰稍调) */
|
body {
|
font-family: 'SimSun', '宋体', 'Songti SC', STSong, 'Times New Roman', serif;
|
font-size: 14px; /* 五号约10.5pt,对应14px清晰 */
|
line-height: 1.5;
|
background: #f5f7fa;
|
margin: 0;
|
min-height: 100vh;
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
padding: 20px;
|
box-sizing: border-box;
|
}
|
.diagram-container {
|
max-width: 1000px;
|
width: 100%;
|
background: white;
|
box-shadow: 0 8px 20px rgba(0,0,0,0.1);
|
border-radius: 16px;
|
padding: 24px 20px 30px 20px;
|
border: 1px solid #e2e8f0;
|
}
|
/* 主标题 */
|
.title {
|
text-align: center;
|
font-size: 20px;
|
font-weight: 600;
|
margin-bottom: 16px;
|
color: #2c3e50;
|
letter-spacing: 1px;
|
border-bottom: 2px solid #42b883;
|
padding-bottom: 10px;
|
}
|
/* 副标题/区域标识 */
|
.section-label {
|
font-weight: 600;
|
color: #2c3e50;
|
background: #edf2f7;
|
display: inline-block;
|
padding: 4px 14px;
|
border-radius: 30px;
|
font-size: 14px;
|
margin-bottom: 12px;
|
border-left: 4px solid #42b883;
|
}
|
/* 三列布局: 左侧优势 + 核心流程 + 右侧ref */
|
.main-panel {
|
display: flex;
|
gap: 16px;
|
margin-top: 10px;
|
flex-wrap: wrap;
|
}
|
.left-panel {
|
flex: 1.2;
|
min-width: 220px;
|
background: #f8fafc;
|
border-radius: 16px;
|
padding: 16px 14px;
|
box-shadow: 0 2px 8px rgba(0,0,0,0.03);
|
border: 1px solid #e9eef3;
|
}
|
.center-panel {
|
flex: 2.2;
|
min-width: 380px;
|
background: #f8fafc;
|
border-radius: 16px;
|
padding: 16px 14px;
|
border: 1px solid #e9eef3;
|
box-shadow: 0 2px 8px rgba(0,0,0,0.03);
|
}
|
.right-panel {
|
flex: 1;
|
min-width: 210px;
|
background: #f8fafc;
|
border-radius: 16px;
|
padding: 16px 14px;
|
border: 1px solid #e9eef3;
|
box-shadow: 0 2px 8px rgba(0,0,0,0.03);
|
}
|
/* 引用标记小字 */
|
.ref-note {
|
color: #64748b;
|
font-size: 12px; /* 稍小但仍保持宋体 */
|
margin-top: 8px;
|
border-top: 1px dashed #cbd5e1;
|
padding-top: 8px;
|
}
|
/* 流程图卡片样式 */
|
.flow-step {
|
background: white;
|
border-radius: 12px;
|
padding: 12px 10px;
|
margin-bottom: 12px;
|
border-left: 5px solid #42b883;
|
box-shadow: 0 2px 6px rgba(0,0,0,0.02);
|
border: 1px solid #dde7f0;
|
border-left-width: 5px;
|
border-left-color: #42b883;
|
position: relative;
|
}
|
.flow-step strong {
|
color: #1e293b;
|
font-weight: 600;
|
display: block;
|
margin-bottom: 5px;
|
font-size: 14.5px;
|
}
|
.flow-step .desc {
|
color: #334155;
|
margin-left: 6px;
|
}
|
/* 箭头连线示意 (简单用字符) */
|
.arrow-down {
|
text-align: center;
|
color: #94a3b8;
|
font-size: 18px;
|
margin: -4px 0 -2px 0;
|
letter-spacing: -2px;
|
}
|
/* 小标签 */
|
.proxy-badge {
|
background: #e0f2fe;
|
color: #0369a1;
|
padding: 2px 10px;
|
border-radius: 20px;
|
font-size: 12px;
|
display: inline-block;
|
margin-right: 8px;
|
}
|
.ref-badge {
|
background: #fef9c3;
|
color: #854d0e;
|
padding: 2px 10px;
|
border-radius: 20px;
|
font-size: 12px;
|
display: inline-block;
|
margin-right: 8px;
|
}
|
.highlight {
|
background: #dcfce7;
|
color: #166534;
|
padding: 2px 8px;
|
border-radius: 16px;
|
font-weight: 500;
|
}
|
/* 列表样式 */
|
ul {
|
margin: 8px 0 0 0;
|
padding-left: 20px;
|
}
|
li {
|
margin-bottom: 5px;
|
color: #2d3c50;
|
}
|
hr {
|
border: none;
|
border-top: 1px solid #d1d9e6;
|
margin: 16px 0 10px 0;
|
}
|
/* 右侧ref特殊盒子 */
|
.ref-box {
|
background: white;
|
border-radius: 12px;
|
padding: 14px;
|
border: 1px dashed #f59e0b;
|
margin: 15px 0 5px;
|
}
|
.ref-value {
|
background: #f1f5f9;
|
font-family: monospace;
|
padding: 4px 8px;
|
border-radius: 20px;
|
display: inline-block;
|
margin: 6px 0;
|
}
|
.auto-unpack {
|
background: #f1f5f9;
|
border-radius: 20px;
|
padding: 4px 12px;
|
font-size: 12px;
|
color: #334155;
|
}
|
/* 五号字体全局已定,保持所有字体系列 */
|
p, li, div, span, .desc, .flow-step, .section-label {
|
font-family: inherit;
|
}
|
/* 小字引用 */
|
.cite {
|
color: #6b7b8f;
|
font-size: 12px;
|
margin-top: 16px;
|
text-align: right;
|
border-top: 1px solid #e2e8f0;
|
padding-top: 12px;
|
}
|
</style>
|
</head>
|
<body>
|
<div class="diagram-container">
|
<!-- 大标题 -->
|
<div class="title">🟢 Vue3 响应式系统原理 · 基于Proxy全面监听</div>
|
|
<!-- 三块内容 -->
|
<div class="main-panel">
|
<!-- 左侧:Proxy优势 -->
|
<div class="left-panel">
|
<div class="section-label">✨ 基于Proxy · 优势</div>
|
<div style="background: #e6f7ff; border-radius: 12px; padding: 12px; border: 1px solid #bae7ff;">
|
<span class="proxy-badge">ES6 Proxy</span><span style="font-weight: 500;"> 取代 Object.defineProperty</span>
|
<ul style="margin-top: 12px;">
|
<li>✅ 拦截对象<strong>各种操作</strong>:读取(get)、设置(set)、删除(deleteProperty)、枚举(ownKeys) 等</li>
|
<li>✅ <strong>全面监听</strong>:属性新增、删除、数组索引/length修改都能捕获</li>
|
<li>✅ 无需递归遍历所有属性,性能更优且支持动态属性</li>
|
<li>✅ 可拦截 <code>in</code> 操作符、<code>for...in</code> 等元操作</li>
|
</ul>
|
<div style="margin-top: 10px; background: #ffffffb3; padding: 8px; border-radius: 8px; font-size: 13px;">
|
<span style="color: #2c3e50;">📋 对比Vue2:</span>无法检测对象属性添加/删除,需用 <code>$set</code>;对数组需重写方法。Proxy完美解决。
|
</div>
|
</div>
|
<!-- 文献引用[10] -->
|
<div class="ref-note">📎 参考文献 [10]: Proxy可以拦截对象的各种操作,包括属性读取、设置、删除、枚举等,实现对对象变化的全面监听。</div>
|
</div>
|
|
<!-- 中间:核心流程 (依赖收集/触发更新) -->
|
<div class="center-panel">
|
<div class="section-label">⚙️ 核心流程 · reactive + 依赖追踪</div>
|
|
<!-- 步骤1:reactive -->
|
<div class="flow-step">
|
<strong>1️⃣ reactive(data) 创建代理对象</strong>
|
<div class="desc">原始对象通过 <code style="background:#e9ecf3; padding:2px 6px;">new Proxy(target, handler)</code> 变为响应式代理。</div>
|
</div>
|
<div class="arrow-down">▼</div>
|
|
<!-- 步骤2:组件渲染/访问 -->
|
<div class="flow-step">
|
<strong>2️⃣ 组件渲染过程中访问响应式数据</strong>
|
<div class="desc">例如模板中使用 <code>{{ state.count }}</code> 或 <code>computed</code> 中读取。</div>
|
</div>
|
<div class="arrow-down">▼</div>
|
|
<!-- 步骤3:getter拦截 & 依赖收集 -->
|
<div class="flow-step" style="border-left-color: #f59e0b;">
|
<strong>3️⃣ <span class="highlight">getter 拦截</span> → 依赖收集</strong>
|
<div class="desc">触发Proxy的 <code>get</code> 陷阱,将当前副作用(如组件的渲染函数)记录为该属性的依赖。</div>
|
<div style="background: #fff7e6; border-radius: 20px; padding: 4px 10px; margin-top: 8px; font-size:13px;">
|
🧠 依赖关系:target Map (属性 → Set(副作用))
|
</div>
|
</div>
|
<div class="arrow-down">▼</div>
|
|
<!-- 步骤4:数据变化 setter -->
|
<div class="flow-step" style="border-left-color: #ef4444;">
|
<strong>4️⃣ 响应式数据变化 🔁 <span class="highlight">setter 拦截</span></strong>
|
<div class="desc">修改属性值触发 <code>set</code> 陷阱,比对变化后进入通知阶段。</div>
|
</div>
|
<div class="arrow-down">▼</div>
|
|
<!-- 步骤5:通知依赖更新 -->
|
<div class="flow-step" style="border-left-color: #3b82f6;">
|
<strong>5️⃣ 触发更新 (通知相关依赖)</strong>
|
<div class="desc">从依赖映射中找到所有与该属性相关的副作用(组件渲染函数、计算属性等),加入调度队列。</div>
|
</div>
|
<div class="arrow-down">▼</div>
|
|
<!-- 步骤6:组件重新渲染 -->
|
<div class="flow-step">
|
<strong>6️⃣ 组件重新渲染 · 更新DOM</strong>
|
<div class="desc">执行副作用,生成新虚拟DOM,高效更新真实DOM。</div>
|
</div>
|
|
<!-- 附加说明:依赖收集与触发更新的简洁描述 -->
|
<hr>
|
<div style="display: flex; gap: 12px; justify-content: space-between; background: #e7f3ff; padding: 10px; border-radius: 12px;">
|
<div><span style="background: #2563eb; color:white; padding:2px 10px; border-radius: 30px;">⬇️ 依赖收集</span> 在getter中建立关系</div>
|
<div>➡️</div>
|
<div><span style="background: #db2777; color:white; padding:2px 10px; border-radius: 30px;">⬆️ 触发更新</span> 在setter中派发任务</div>
|
</div>
|
</div>
|
|
<!-- 右侧:ref概念 -->
|
<div class="right-panel">
|
<div class="section-label">🔁 ref 包装基本类型</div>
|
<div style="background: #fffbeb; border-radius: 14px; padding: 12px; border:1px solid #fde68a;">
|
<span class="ref-badge">ref()</span> 用于 <code>string | number | boolean</code> 等原始值
|
<div class="ref-box">
|
<div style="display: flex; align-items: center; gap: 6px;">
|
<span>🔸 构造:</span>
|
<code style="background:#e9eef3; padding:4px 8px; border-radius: 20px;">ref('hello')</code>
|
</div>
|
<div style="margin: 12px 0 8px 0; background:#e6f7ff; padding: 8px; border-radius: 12px;">
|
⚡ 返回一个包含 <strong style="color:#b45309;">.value</strong> 属性的对象:
|
<div style="background: #1e293b; color: #facc15; padding: 6px 12px; border-radius: 20px; margin-top: 6px; font-family: monospace;">
|
{ value: 'hello' }
|
</div>
|
</div>
|
<div><span class="auto-unpack">✨ 模板中自动解包</span></div>
|
<div style="margin-top: 10px; background:#fff; border-radius: 20px; padding: 6px 10px;">
|
✅ <code><div>{{ msg }}</div></code> 无需写 <code>msg.value</code>
|
</div>
|
<div style="margin-top: 10px; border-top: 1px dotted #cbd5e1; padding-top: 8px;">
|
🧩 <strong>script中必须使用 .value</strong> 修改/读取
|
</div>
|
</div>
|
<div style="font-size: 13px; margin-top: 8px; background: #d9f99d; padding: 4px 8px; border-radius: 20px;">
|
📦 同样基于Proxy/reactive实现,但通过value属性嵌套保证响应式
|
</div>
|
</div>
|
<!-- 简洁对比 -->
|
<div style="margin-top: 16px;">
|
<span style="background: #cbd5e1; padding:2px 10px; border-radius: 30px;">reactive vs ref</span>
|
<ul style="margin-top: 10px;">
|
<li><strong>reactive</strong>:用于对象/数组,直接代理</li>
|
<li><strong>ref</strong>:用于基本类型,也可包裹对象(但自动用reactive转换深层)</li>
|
</ul>
|
</div>
|
</div>
|
</div>
|
|
<!-- 底部整合描述:完全呼应题目内容 -->
|
<div style="display: flex; flex-wrap: wrap; gap: 16px; margin-top: 24px; background: #f1f5f9; border-radius: 20px; padding: 14px 18px;">
|
<div style="flex:1; min-width: 200px;">
|
<span style="font-weight: 600;">📌 Proxy全面监听</span>:get/set/deleteProperty/ownKeys · 全面代替Vue2的Object.defineProperty
|
</div>
|
<div style="flex:1; min-width: 220px;">
|
<span style="font-weight: 600;">🎯 核心流程闭环</span>:reactive → 渲染访问 → getter收集 → setter触发 → 通知更新 → 重绘
|
</div>
|
<div style="flex:1; min-width: 180px;">
|
<span style="font-weight: 600;">🔖 ref自动解包</span>:模板中直接使用标识符,底层访问.value
|
</div>
|
</div>
|
|
<!-- 完整引用及字体声明 -->
|
<!-- <div class="cite">
|
<span>📖 内容依据:Vue3响应式原理 · 基于Proxy的优势、核心流程 (依赖收集/触发更新) 及 ref 包装基本类型。 </span>
|
<span style="margin-left: 12px;">🔤 全图字体: 宋体 (SimSun) 五号 (14px)</span>
|
</div> -->
|
</div>
|
<!-- 确保任何位置都强制宋体(备用) -->
|
<div style="display: none;">字体检测</div>
|
</body>
|
</html>
|