perf(finder,orphan): 第一層掃描提速——category 記憶化(S3)+orphan 葉塊預計算(S2),保行為
下游效能訴求一(掃描疑 O(n²))之第一層:無狀態、保行為、無公開 API 變更之確定收益。 S3 category 記憶化:finder.category 以 categoryCache(WeakMap<Element, InlineCategory>)記憶化,砍 除 collectRuns/jiyaAdjacency/jinze/split 跨 pass 對同一元素之重複 el.matches()(pill/isolate/ avoid 選擇器)。安全:單次 render 內一元素之分類恆定(class/skip 屬性/nodeName 不因 render 變更; jz-* 包裹按 nodeName 分類亦恆定),且 Finder 隨 makeCtx 每次 render 重建 → 快取僅存活單次 render、 無跨 render 失效(持久化屬 C1,另議)。亲驗(正確補 Element.prototype.matches):pill/isolate 密集 頁面 el.matches() 8204→5842(約降三成);暫禁快取則 computeCategory 2322 vs 1041(命中率 55%)。 S2 orphan 葉塊預計算:以一趟上攀預計算「非葉塊」Set 取代逐塊 block.querySelector(sel) 掃整棵子樹之 O(Σ塊子樹)(深塊嵌套下 O(n²));某塊為另一塊之最近塊祖先 ⟺ 含塊級後代,scope 無涉、與原 querySelector 同口徑,行為等價。sel 亦外提出迴圈。 測試 129→130(+1 鎖定 S2 非葉塊等價:含巢狀塊之容器跳過、僅內層葉塊綁定);全綠、輸出不變。 文檔:ARCHITECTURE §4.1(category 記憶化)+§6.5.1(orphan 葉塊預計算)。 新發現(待議、非本層):剩餘 el.matches() 之最大來源為 insidePill 之「逐文本節點沿祖先鏈 pillMatches」 (eachTextNode 每文本節點一趟、未記憶化)——可作下一項提速機會。
This commit is contained in:
Vendored
+9
@@ -31,6 +31,7 @@ export declare class Finder {
|
||||
private readonly levelTextSelector;
|
||||
private readonly featureCache;
|
||||
private readonly levelCache;
|
||||
private readonly categoryCache;
|
||||
constructor(opts: ResolvedOptions);
|
||||
/** 該元素是否匹配 pill 選擇器(三分類之「整體」:code/kbd/行內公式等)。 */
|
||||
pillMatches(el: Element): boolean;
|
||||
@@ -49,8 +50,16 @@ export declare class Finder {
|
||||
* (自身排除最高)。**邏輯文本流之遍歷原語**(collectRuns/jiyaAdjacency/adjacentLogicalChar
|
||||
* 之 isTransparent)皆據此**單一分類**分派;core/split 之 canSplit = SPLITTABLE 白名單 ∩
|
||||
* transparent(結構切分為獨立層,故更窄)。
|
||||
*
|
||||
* **記憶化**(S3):分類含至多三次 el.matches()(pill/isolate/avoid 選擇器),且被
|
||||
* collectRuns/jiyaAdjacency/jinze/orphan/split 各 pass 逐元素重算——以 categoryCache
|
||||
* 記憶化砍除跨 pass 重複。安全性:單次 render 內一元素之分類**恆定**(class/skip 屬性/
|
||||
* nodeName 皆不因 render 變更;jz-* 包裹為新建元素、按 nodeName 分類亦恆定),且 Finder
|
||||
* 隨 makeCtx 每次 render 重建 → 快取僅存活單次 render,無跨 render 失效問題(持久化屬 C1,
|
||||
* 另議)。
|
||||
*/
|
||||
category(el: Element): InlineCategory;
|
||||
private computeCategory;
|
||||
/** category 之透明判定末步:styleInlines 標籤,或聚珍自身之 wrap 類 jz-*(jz-hws 間隙、
|
||||
* jz-jinze 禁則群組、**jz-char/jz-inner 標點原子**)。前四步已排除 avoid/pill/isolate/block。
|
||||
* jz-char/jz-inner 必須列入:否則 adjacentLogicalChar 之 descend 遇標點原子既不進入亦不
|
||||
|
||||
Vendored
+32
-2
@@ -42,6 +42,7 @@ var Juzhen = (() => {
|
||||
this.includeSelector = opts.scope.include;
|
||||
this.featureCache = /* @__PURE__ */ new WeakMap();
|
||||
this.levelCache = /* @__PURE__ */ new WeakMap();
|
||||
this.categoryCache = /* @__PURE__ */ new WeakMap();
|
||||
}
|
||||
/** 該元素是否匹配 pill 選擇器(三分類之「整體」:code/kbd/行內公式等)。 */
|
||||
pillMatches(el) {
|
||||
@@ -73,8 +74,24 @@ var Juzhen = (() => {
|
||||
* (自身排除最高)。**邏輯文本流之遍歷原語**(collectRuns/jiyaAdjacency/adjacentLogicalChar
|
||||
* 之 isTransparent)皆據此**單一分類**分派;core/split 之 canSplit = SPLITTABLE 白名單 ∩
|
||||
* transparent(結構切分為獨立層,故更窄)。
|
||||
*
|
||||
* **記憶化**(S3):分類含至多三次 el.matches()(pill/isolate/avoid 選擇器),且被
|
||||
* collectRuns/jiyaAdjacency/jinze/orphan/split 各 pass 逐元素重算——以 categoryCache
|
||||
* 記憶化砍除跨 pass 重複。安全性:單次 render 內一元素之分類**恆定**(class/skip 屬性/
|
||||
* nodeName 皆不因 render 變更;jz-* 包裹為新建元素、按 nodeName 分類亦恆定),且 Finder
|
||||
* 隨 makeCtx 每次 render 重建 → 快取僅存活單次 render,無跨 render 失效問題(持久化屬 C1,
|
||||
* 另議)。
|
||||
*/
|
||||
category(el) {
|
||||
const cached = this.categoryCache.get(el);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const result = this.computeCategory(el);
|
||||
this.categoryCache.set(el, result);
|
||||
return result;
|
||||
}
|
||||
computeCategory(el) {
|
||||
if (this.avoidsSelf(el)) {
|
||||
return "avoid";
|
||||
}
|
||||
@@ -1692,6 +1709,20 @@ var Juzhen = (() => {
|
||||
const anyCjk = new RegExp("[" + options.ruleset.cjk + "]");
|
||||
const biaodian = options.ruleset.biaodian;
|
||||
const chars = options.orphan.chars;
|
||||
const sel = Array.from(blockSet).map((t) => t.toLowerCase()).join(",");
|
||||
const nonLeaf = /* @__PURE__ */ new Set();
|
||||
if (sel) {
|
||||
ctx.root.querySelectorAll(sel).forEach((b) => {
|
||||
let p = b.parentElement;
|
||||
while (p) {
|
||||
if (blockSet.has(p.nodeName)) {
|
||||
nonLeaf.add(p);
|
||||
break;
|
||||
}
|
||||
p = p.parentElement;
|
||||
}
|
||||
});
|
||||
}
|
||||
finder.eachBlock(ctx.root, (block) => {
|
||||
if (!finder.levelAllows(block, "paragraph")) {
|
||||
return;
|
||||
@@ -1702,8 +1733,7 @@ var Juzhen = (() => {
|
||||
if (finder.isAvoided(block)) {
|
||||
return;
|
||||
}
|
||||
const sel = Array.from(blockSet).map((t) => t.toLowerCase()).join(",");
|
||||
if (sel && block.querySelector(sel)) {
|
||||
if (nonLeaf.has(block)) {
|
||||
return;
|
||||
}
|
||||
processBlock(block, finder, blockSet, anyCjk, biaodian, chars);
|
||||
|
||||
Vendored
+32
-2
@@ -15,6 +15,7 @@ var Finder = class {
|
||||
this.includeSelector = opts.scope.include;
|
||||
this.featureCache = /* @__PURE__ */ new WeakMap();
|
||||
this.levelCache = /* @__PURE__ */ new WeakMap();
|
||||
this.categoryCache = /* @__PURE__ */ new WeakMap();
|
||||
}
|
||||
/** 該元素是否匹配 pill 選擇器(三分類之「整體」:code/kbd/行內公式等)。 */
|
||||
pillMatches(el) {
|
||||
@@ -46,8 +47,24 @@ var Finder = class {
|
||||
* (自身排除最高)。**邏輯文本流之遍歷原語**(collectRuns/jiyaAdjacency/adjacentLogicalChar
|
||||
* 之 isTransparent)皆據此**單一分類**分派;core/split 之 canSplit = SPLITTABLE 白名單 ∩
|
||||
* transparent(結構切分為獨立層,故更窄)。
|
||||
*
|
||||
* **記憶化**(S3):分類含至多三次 el.matches()(pill/isolate/avoid 選擇器),且被
|
||||
* collectRuns/jiyaAdjacency/jinze/orphan/split 各 pass 逐元素重算——以 categoryCache
|
||||
* 記憶化砍除跨 pass 重複。安全性:單次 render 內一元素之分類**恆定**(class/skip 屬性/
|
||||
* nodeName 皆不因 render 變更;jz-* 包裹為新建元素、按 nodeName 分類亦恆定),且 Finder
|
||||
* 隨 makeCtx 每次 render 重建 → 快取僅存活單次 render,無跨 render 失效問題(持久化屬 C1,
|
||||
* 另議)。
|
||||
*/
|
||||
category(el) {
|
||||
const cached = this.categoryCache.get(el);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const result = this.computeCategory(el);
|
||||
this.categoryCache.set(el, result);
|
||||
return result;
|
||||
}
|
||||
computeCategory(el) {
|
||||
if (this.avoidsSelf(el)) {
|
||||
return "avoid";
|
||||
}
|
||||
@@ -1665,6 +1682,20 @@ var orphanPass = {
|
||||
const anyCjk = new RegExp("[" + options.ruleset.cjk + "]");
|
||||
const biaodian = options.ruleset.biaodian;
|
||||
const chars = options.orphan.chars;
|
||||
const sel = Array.from(blockSet).map((t) => t.toLowerCase()).join(",");
|
||||
const nonLeaf = /* @__PURE__ */ new Set();
|
||||
if (sel) {
|
||||
ctx.root.querySelectorAll(sel).forEach((b) => {
|
||||
let p = b.parentElement;
|
||||
while (p) {
|
||||
if (blockSet.has(p.nodeName)) {
|
||||
nonLeaf.add(p);
|
||||
break;
|
||||
}
|
||||
p = p.parentElement;
|
||||
}
|
||||
});
|
||||
}
|
||||
finder.eachBlock(ctx.root, (block) => {
|
||||
if (!finder.levelAllows(block, "paragraph")) {
|
||||
return;
|
||||
@@ -1675,8 +1706,7 @@ var orphanPass = {
|
||||
if (finder.isAvoided(block)) {
|
||||
return;
|
||||
}
|
||||
const sel = Array.from(blockSet).map((t) => t.toLowerCase()).join(",");
|
||||
if (sel && block.querySelector(sel)) {
|
||||
if (nonLeaf.has(block)) {
|
||||
return;
|
||||
}
|
||||
processBlock(block, finder, blockSet, anyCjk, biaodian, chars);
|
||||
|
||||
Reference in New Issue
Block a user