feat(stages): 添加性能测试和虚拟DOM实现阶段
添加stage04-perf用于真实DOM操作性能对比,包含三个不同的更新策略: - innerHTML全量更新 - createElement全量重建 - 精确单节点更新 添加stage04用于模拟异步接口数据获取后的渲染演示 添加stage05实现虚拟DOM基础功能,提供VNode对象描述DOM树结构和递 归渲染函数
This commit is contained in:
43
stage04-perf/index.css
Normal file
43
stage04-perf/index.css
Normal file
@@ -0,0 +1,43 @@
|
||||
.list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: #ddd;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.content h4 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.content p {
|
||||
margin: 4px 0;
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.active {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.inactive {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
28
stage04-perf/index.html
Normal file
28
stage04-perf/index.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Stage 04-perf - 真实 DOM 操作代价</title>
|
||||
<link rel="stylesheet" href="index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h2>真实 DOM 更新性能对比(控制台查看详细耗时)</h2>
|
||||
<div>
|
||||
<label for="rangeN">数据量 N:<span id="nValue">10000</span></label>
|
||||
<br>
|
||||
<input type="range" id="rangeN" min="100" max="200000" step="100" value="10000" />
|
||||
<button id="btnApplyN">应用此数量</button>
|
||||
</div>
|
||||
<div class="stats" id="stats">点击按钮对比耗时</div>
|
||||
<div id="compareDisplay" style="margin: 8px 0; font-family: monospace;"></div>
|
||||
<div>
|
||||
<button id="btnFullInnerHTML">全量 innerHTML 更新</button>
|
||||
<button id="btnFullCreate">全量 createElement 更新</button>
|
||||
<button id="btnExact">精确单节点更新</button>
|
||||
<button id="btnReset">重置列表</button>
|
||||
</div>
|
||||
<ul id="list"></ul>
|
||||
<script src="./index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
210
stage04-perf/index.js
Normal file
210
stage04-perf/index.js
Normal file
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* Stage 04-perf:真实 DOM 操作性能实证(耗时对比版)
|
||||
* 动态调整数据量,复杂节点,三种更新策略耗时一目了然
|
||||
*/
|
||||
|
||||
const listEl = document.getElementById("list");
|
||||
const statsEl = document.getElementById("stats");
|
||||
const compareDisplay = document.getElementById("compareDisplay");
|
||||
const rangeN = document.getElementById("rangeN");
|
||||
const nValueSpan = document.getElementById("nValue");
|
||||
|
||||
// ---------- 当前数据量(与滑块同步)----------
|
||||
let N = 10000;
|
||||
let items = [];
|
||||
|
||||
// 记录最近一次各方案耗时(ms),用于页面展示
|
||||
const lastResult = {
|
||||
innerHTML: "-",
|
||||
createElement: "-",
|
||||
exact: "-",
|
||||
};
|
||||
|
||||
// ---------- 工具函数:生成初始数据 ----------
|
||||
function generateItems(count) {
|
||||
return Array.from({ length: count }, (_, i) => ({
|
||||
id: i,
|
||||
title: `Item ${i}`,
|
||||
desc: `Description for item ${i}`,
|
||||
tag: i % 3 === 0 ? "active" : "inactive",
|
||||
}));
|
||||
}
|
||||
|
||||
// ---------- 复杂节点构造 ----------
|
||||
/**
|
||||
* 生成带子元素的 <li>,模拟真实 UI 片段
|
||||
* @param {Object} item
|
||||
* @returns {HTMLLIElement}
|
||||
*/
|
||||
function createComplexItem(item) {
|
||||
const li = document.createElement("li");
|
||||
li.className = "list-item";
|
||||
|
||||
const avatar = document.createElement("div");
|
||||
avatar.className = "avatar";
|
||||
avatar.textContent = item.id % 100;
|
||||
|
||||
const content = document.createElement("div");
|
||||
content.className = "content";
|
||||
|
||||
const title = document.createElement("h4");
|
||||
title.textContent = item.title;
|
||||
|
||||
const desc = document.createElement("p");
|
||||
desc.textContent = item.desc;
|
||||
|
||||
const tag = document.createElement("span");
|
||||
tag.className = "tag " + item.tag;
|
||||
tag.textContent = item.tag;
|
||||
|
||||
content.appendChild(title);
|
||||
content.appendChild(desc);
|
||||
content.appendChild(tag);
|
||||
li.appendChild(avatar);
|
||||
li.appendChild(content);
|
||||
return li;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建整个列表的 HTML 字符串(innerHTML 方案)
|
||||
* @param {Object[]} data
|
||||
* @returns {string}
|
||||
*/
|
||||
function buildHTML(data) {
|
||||
let html = "";
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const item = data[i];
|
||||
html += `<li class="list-item">
|
||||
<div class="avatar">${item.id % 100}</div>
|
||||
<div class="content">
|
||||
<h4>${item.title}</h4>
|
||||
<p>${item.desc}</p>
|
||||
<span class="tag ${item.tag}">${item.tag}</span>
|
||||
</div>
|
||||
</li>`;
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* 全量 createElement 重建列表
|
||||
* @param {Object[]} data
|
||||
*/
|
||||
function rebuildWithCreateElement(data) {
|
||||
listEl.innerHTML = "";
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
listEl.appendChild(createComplexItem(data[i]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 精确更新单个项目的内容(只改文字)
|
||||
* @param {number} index
|
||||
* @param {Object} newItem
|
||||
*/
|
||||
function updateOneItemExact(index, newItem) {
|
||||
const li = listEl.children[index];
|
||||
if (!li) return;
|
||||
const h4 = li.querySelector("h4");
|
||||
if (h4) h4.textContent = newItem.title;
|
||||
const p = li.querySelector("p");
|
||||
if (p) p.textContent = newItem.desc;
|
||||
const span = li.querySelector("span");
|
||||
if (span) {
|
||||
span.textContent = newItem.tag;
|
||||
span.className = "tag " + newItem.tag;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 更新页面上的耗时对比显示 ----------
|
||||
function updateCompareDisplay() {
|
||||
compareDisplay.innerHTML = `
|
||||
最近耗时对比(N = ${N})<br>
|
||||
innerHTML 全量:<b>${lastResult.innerHTML}ms</b> |
|
||||
createElement 全量:<b>${lastResult.createElement}ms</b> |
|
||||
精确更新:<b>${lastResult.exact}ms</b>
|
||||
`;
|
||||
}
|
||||
|
||||
// ---------- 重置列表到当前 N ----------
|
||||
function resetList() {
|
||||
items = generateItems(N);
|
||||
console.time(`reset (${N} 项 createElement)`);
|
||||
rebuildWithCreateElement(items);
|
||||
console.timeEnd(`reset (${N} 项 createElement)`);
|
||||
statsEl.textContent = `列表已重置(${N} 项)`;
|
||||
// 清空耗时记录
|
||||
lastResult.innerHTML = "-";
|
||||
lastResult.createElement = "-";
|
||||
lastResult.exact = "-";
|
||||
updateCompareDisplay();
|
||||
}
|
||||
|
||||
// ---------- 滑块事件 ----------
|
||||
// 滑块仅更新显示数值,不自动重建
|
||||
rangeN.addEventListener("input", (e) => {
|
||||
N = parseInt(e.target.value, 10);
|
||||
nValueSpan.textContent = N;
|
||||
});
|
||||
|
||||
// 点击“应用”按钮才重建列表
|
||||
document.getElementById("btnApplyN").addEventListener("click", () => {
|
||||
resetList();
|
||||
});
|
||||
|
||||
// ---------- 按钮事件 ----------
|
||||
document.getElementById("btnReset").addEventListener("click", resetList);
|
||||
|
||||
document.getElementById("btnFullInnerHTML").addEventListener("click", () => {
|
||||
const newItems = items.map((item, i) =>
|
||||
i === 5000 ? { ...item, title: "CHANGED" } : item,
|
||||
);
|
||||
|
||||
console.time(`innerHTML 全量 (${N} 项)`);
|
||||
listEl.innerHTML = buildHTML(newItems);
|
||||
console.timeEnd(`innerHTML 全量 (${N} 项)`);
|
||||
|
||||
// 从控制台取不到精确时间,用 performance.now 手动记录
|
||||
const start = performance.now();
|
||||
listEl.innerHTML = buildHTML(newItems);
|
||||
const end = performance.now();
|
||||
lastResult.innerHTML = (end - start).toFixed(2);
|
||||
items = newItems;
|
||||
statsEl.textContent = "innerHTML 更新完成";
|
||||
updateCompareDisplay();
|
||||
});
|
||||
|
||||
document.getElementById("btnFullCreate").addEventListener("click", () => {
|
||||
const newItems = items.map((item, i) =>
|
||||
i === 5000 ? { ...item, title: "CHANGED" } : item,
|
||||
);
|
||||
|
||||
const start = performance.now();
|
||||
rebuildWithCreateElement(newItems);
|
||||
const end = performance.now();
|
||||
lastResult.createElement = (end - start).toFixed(2);
|
||||
console.log(`createElement 全量耗时: ${lastResult.createElement}ms`);
|
||||
|
||||
items = newItems;
|
||||
statsEl.textContent = "createElement 全量完成";
|
||||
updateCompareDisplay();
|
||||
});
|
||||
|
||||
document.getElementById("btnExact").addEventListener("click", () => {
|
||||
const targetIndex = Math.min(5000, N - 1);
|
||||
const newItems = [...items];
|
||||
newItems[targetIndex] = { ...newItems[targetIndex], title: "CHANGED" };
|
||||
|
||||
const start = performance.now();
|
||||
updateOneItemExact(targetIndex, newItems[targetIndex]);
|
||||
const end = performance.now();
|
||||
lastResult.exact = (end - start).toFixed(2);
|
||||
console.log(`精确更新耗时: ${lastResult.exact}ms`);
|
||||
|
||||
items = newItems;
|
||||
statsEl.textContent = "精确更新完成";
|
||||
updateCompareDisplay();
|
||||
});
|
||||
|
||||
// ---------- 启动 ----------
|
||||
resetList();
|
||||
Reference in New Issue
Block a user