Files
minivue/stage04-perf/index.js
zzy 845789cd60 feat(stages): 添加性能测试和虚拟DOM实现阶段
添加stage04-perf用于真实DOM操作性能对比,包含三个不同的更新策略:
- innerHTML全量更新
- createElement全量重建
- 精确单节点更新

添加stage04用于模拟异步接口数据获取后的渲染演示

添加stage05实现虚拟DOM基础功能,提供VNode对象描述DOM树结构和递
归渲染函数
2026-05-02 12:36:53 +08:00

211 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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();