release: v1.0.14 By: AICoder

This commit is contained in:
AI Engineer 2026-06-05 19:16:52 +08:00
parent b04be8d437
commit a777f315b4
8 changed files with 31 additions and 9 deletions

View File

@ -1,5 +1,11 @@
# CHANGELOG
## v1.0.14 (2026-06-05)
### 修复
- **微任务扫描死循环**: 引入 `_stScanned` 标记对新挂载节点进行幂等拦截,防止在复杂嵌套(如 `$if` / `$each`)及大数据量场景下,新老节点重组重排循环触发 MutationObserver 造成浏览器卡死的微任务无限扫描死循环。
- **状态更新防御**: 在 `_updateBinding` 逻辑中加入同值校验,避免当目标值与原值相等时重复赋值,杜绝不必要的 Observer 触发与重绘。
## v1.0.13 (2026-05-26)
### 新特性

10
dist/state.js vendored
View File

@ -244,7 +244,9 @@
if (typeof result === "object" && result != null && !Array.isArray(result) && o[lk] == null) o[lk] = {};
const lo = o[lk];
if (typeof lo === "object" && lo != null && lo.__watch) Object.assign(lo, result);
else o[lk] = result;
else {
if (o[lk] !== result) o[lk] = result;
}
} else if (typeof result === "object" && result != null && !Array.isArray(result)) {
Object.assign(o, result);
}
@ -475,6 +477,7 @@
return;
}
if (node.nodeType !== 1) return;
node._stScanned = true;
if (!node._stTranslated) {
Array.from(node.attributes).forEach((attr) => {
if (!attr.name.startsWith("$") && !attr.name.startsWith("st-") && !attr.name.startsWith(".")) {
@ -693,7 +696,10 @@
new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((newNode) => {
if (newNode.isConnected) _scanTree(newNode);
if (newNode.isConnected && newNode.nodeType === 1 && !newNode._stScanned) {
newNode._stScanned = true;
_scanTree(newNode);
}
});
mutation.removedNodes.forEach((oldNode) => _unbindTree(oldNode));
});

2
dist/state.min.js vendored

File diff suppressed because one or more lines are too long

2
dist/state.min.mjs vendored

File diff suppressed because one or more lines are too long

10
dist/state.mjs vendored
View File

@ -240,7 +240,9 @@ function _updateBinding(binding) {
if (typeof result === "object" && result != null && !Array.isArray(result) && o[lk] == null) o[lk] = {};
const lo = o[lk];
if (typeof lo === "object" && lo != null && lo.__watch) Object.assign(lo, result);
else o[lk] = result;
else {
if (o[lk] !== result) o[lk] = result;
}
} else if (typeof result === "object" && result != null && !Array.isArray(result)) {
Object.assign(o, result);
}
@ -471,6 +473,7 @@ const _scanTree = (node, scanObj = {}) => {
return;
}
if (node.nodeType !== 1) return;
node._stScanned = true;
if (!node._stTranslated) {
Array.from(node.attributes).forEach((attr) => {
if (!attr.name.startsWith("$") && !attr.name.startsWith("st-") && !attr.name.startsWith(".")) {
@ -689,7 +692,10 @@ if (typeof document !== "undefined") {
new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((newNode) => {
if (newNode.isConnected) _scanTree(newNode);
if (newNode.isConnected && newNode.nodeType === 1 && !newNode._stScanned) {
newNode._stScanned = true;
_scanTree(newNode);
}
});
mutation.removedNodes.forEach((oldNode) => _unbindTree(oldNode));
});

View File

@ -1,6 +1,6 @@
{
"name": "@apigo.cc/state",
"version": "1.0.13",
"version": "1.0.14",
"type": "module",
"main": "dist/state.js",
"module": "dist/state.js",

View File

@ -76,7 +76,7 @@ export function _updateBinding(binding) {
if (typeof result === 'object' && result != null && !Array.isArray(result) && o[lk] == null) o[lk] = {};
const lo = o[lk];
if (typeof lo === 'object' && lo != null && lo.__watch) Object.assign(lo, result);
else o[lk] = result;
else { if (o[lk] !== result) o[lk] = result; }
} else if (typeof result === 'object' && result != null && !Array.isArray(result)) {
Object.assign(o, result);
}
@ -308,6 +308,7 @@ export const _scanTree = (node, scanObj = {}) => {
node._stTranslated = true; return;
}
if (node.nodeType !== 1) return;
node._stScanned = true;
if (!node._stTranslated) {
Array.from(node.attributes).forEach(attr => {

View File

@ -28,7 +28,10 @@ if (typeof document !== 'undefined') {
new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(newNode => {
if (newNode.isConnected) _scanTree(newNode);
if (newNode.isConnected && newNode.nodeType === 1 && !newNode._stScanned) {
newNode._stScanned = true;
_scanTree(newNode);
}
});
mutation.removedNodes.forEach(oldNode => _unbindTree(oldNode));
});