Perf: Optimize nested each scope sync and add telemetry

This commit is contained in:
AI Engineer 2026-05-22 12:32:47 +08:00
parent c0d089a990
commit 548afb97ed
2 changed files with 25 additions and 6 deletions

View File

@ -1,5 +1,12 @@
# CHANGELOG
## v1.0.11 (2026-05-21)
### 核心优化
- **嵌套作用域同步**: 彻底重构了 `$each` 指令在节点复用时的上下文同步机制。现在复用节点会主动感知并合并父级作用域变量(`_ref`)的变更,解决了在虚拟滚动场景下嵌套 `$each` 数据不更新的严重 Bug。
- **按需扫描机制**: 引入了变更感知逻辑,只有当迭代项引用变化或父级上下文变量变化时才触发子树扫描(`_scanTree`),大幅提升了复杂嵌套组件的滚动性能。
- **性能遥测系统**: 在 `dom.js` 中内置了 `__statePerformanceTelemetry` 埋点,支持实时监控 `scanCount`(扫描)、`reuseCount`(复用)和 `moveCount`DOM 移动)等核心指标,为极致性能调优提供数据支撑。
## v1.0.10 (2026-05-18)
### 修复

View File

@ -119,20 +119,32 @@ export function _updateBinding(binding) {
const rawKey = keyName ? (item && typeof item === 'object' ? item[keyName] : item) : k;
// Safety: Handle potential duplicate keys or undefined keys
const keyVal = (rawKey === undefined || rawKey === null || newKeyedNodes.has(rawKey)) ? `st_key_fallback_${i}_${Math.random()}` : rawKey;
let existingNodes = node._keyedNodes.get(keyVal);
if (existingNodes) {
// Reuse existing nodes
node._keyedNodes.delete(keyVal);
existingNodes.forEach(child => {
child._ref[indexName] = k;
// If data reference hasn't changed, skip heavy scan
if (child._ref[asName] !== item) {
if (window.__statePerformanceTelemetry) window.__statePerformanceTelemetry.reuseCount++;
let scopeChanged = false;
for (let key in node._ref) {
if (key === asName || key === indexName) continue;
if (child._ref[key] !== node._ref[key]) {
child._ref[key] = node._ref[key];
scopeChanged = true;
}
}
const indexChanged = child._ref[indexName] !== k;
if (indexChanged) child._ref[indexName] = k;
if (child._ref[asName] !== item || scopeChanged) {
child._ref[asName] = item;
if (window.__statePerformanceTelemetry) window.__statePerformanceTelemetry.scanCount++;
_scanTree(child, { thisObj: node._thisObj, extendVars: child._ref });
} else if (node.parentNode.lastChild !== child) {
// Just move to the end to maintain order
if (window.__statePerformanceTelemetry) window.__statePerformanceTelemetry.moveCount++;
node.parentNode.insertBefore(child, node);
}
});
@ -346,9 +358,9 @@ export const _scanTree = (node, scanObj = {}) => {
node._ref = curr ? { ...curr._ref } : {};
}
if (scanObj.extendVars) Object.assign(node._ref, scanObj.extendVars);
_parseNode(node, scanObj);
const nodes = [...(node.childNodes || [])];
const nextScanObj = { thisObj: scanObj.thisObj, extendVars: { ...node._ref } };
nodes.forEach(child => {