/**
* 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",
}));
}
// ---------- 复杂节点构造 ----------
/**
* 生成带子元素的
,模拟真实 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 += `
${item.id % 100}
${item.title}
${item.desc}
${item.tag}
`;
}
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})
innerHTML 全量:${lastResult.innerHTML}ms |
createElement 全量:${lastResult.createElement}ms |
精确更新:${lastResult.exact}ms
`;
}
// ---------- 重置列表到当前 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();