// 增量恢復基準(jsdom)。度量「頁面局部變化」之恢復成本:全樹 revert+render(舊法) // vs 局部 rerender(第三層 C3/C2)。確定性 el.matches() 計數 + 計時中位數。 // // 用法:node test/bench/bench-incremental.mjs [blocks] [iters] import { JSDOM } from "jsdom"; import { createJuzhen } from "../../dist/juzhen.mjs"; const BLOCKS = Number(process.argv[2] || 400); const ITERS = Number(process.argv[3] || 7); function buildHtml(n) { let html = '
'; for (let i = 0; i < n; i += 1) { html += '

第' + i + '段落文字粗體與 Latin word 混排,' + '句內點號、頓號;「引號」(括號)末尾實義字甲乙丙丁。

'; } html += '
'; return html; } const HTML = buildHtml(BLOCKS); function fresh() { const dom = new JSDOM("" + HTML + ""); globalThis.document = dom.window.document; globalThis.NodeFilter = dom.window.NodeFilter; globalThis.Node = dom.window.Node; globalThis.window = dom.window; globalThis.MutationObserver = dom.window.MutationObserver; return dom; } function patchMatches(dom, counter) { let p = dom.window.document.querySelector("strong"); let proto = Object.getPrototypeOf(p); while (proto && !Object.prototype.hasOwnProperty.call(proto, "matches")) { proto = Object.getPrototypeOf(proto); } const orig = proto.matches; proto.matches = function (s) { counter.n += 1; return orig.call(this, s); }; return () => { proto.matches = orig; }; } const median = (a) => a.slice().sort((x, y) => x - y)[Math.floor(a.length / 2)]; // ---- 場景:編輯「中間某一段」後恢復 ---- function editTarget(doc) { return doc.querySelectorAll("p")[Math.floor(BLOCKS / 2)]; } // 全樹法:revert(root)+render(root)。 function fullCost(measureMatches) { const times = []; let matches = 0; for (let k = 0; k < ITERS; k += 1) { const dom = fresh(); const doc = dom.window.document; const jz = createJuzhen(); jz.render(doc.body); const p = editTarget(doc); p.textContent = "改後第 K 段,新的實義字戊己庚辛。"; let restore, counter; if (measureMatches && k === 0) { counter = { n: 0 }; restore = patchMatches(dom, counter); } const t0 = performance.now(); jz.revert(doc.body); jz.render(doc.body); times.push(performance.now() - t0); if (restore) { restore(); matches = counter.n; } } return { ms: median(times), matches }; } // 增量法:rerender(改動塊)。 function incCost(measureMatches) { const times = []; let matches = 0; for (let k = 0; k < ITERS; k += 1) { const dom = fresh(); const doc = dom.window.document; const jz = createJuzhen(); jz.render(doc.body); const p = editTarget(doc); p.textContent = "改後第 K 段,新的實義字戊己庚辛。"; let restore, counter; if (measureMatches && k === 0) { counter = { n: 0 }; restore = patchMatches(dom, counter); } const t0 = performance.now(); jz.rerender(p); times.push(performance.now() - t0); if (restore) { restore(); matches = counter.n; } } return { ms: median(times), matches }; } const full = fullCost(true); const inc = incCost(true); console.log("增量恢復基準(" + BLOCKS + " 段;編輯中間一段後恢復):"); console.log(" 全樹 revert+render :", full.ms.toFixed(2), "ms |", "el.matches()", full.matches); console.log(" 局部 rerender(塊) :", inc.ms.toFixed(2), "ms |", "el.matches()", inc.matches); console.log(" 提速:時間 " + (full.ms / inc.ms).toFixed(1) + "× | matches " + (full.matches / Math.max(1, inc.matches)).toFixed(1) + "×");