| New file |
| | |
| | | |
| | | <!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> |