use*_*170 -2 javascript dom closures weak-references
对于一个玩具示例,假设我有一个时钟小部件:
\n{\n const clockElem = document.getElementById(\'clock\');\n\n const timefmt = new Intl.DateTimeFormat(\n \'default\', { timeStyle: \'medium\', });\n\n setInterval(() => {\n const d = new Date;\n console.log(\'tick\', d, clockElem);\n clockElem.querySelector(\'p\').innerHTML =\n timefmt.format(d);\n }, 1000);\n\n clockElem.querySelector(\'button\')\n .addEventListener(\'click\', ev => {\n clockElem.remove();\n });\n}
Run Code Online (Sandbox Code Playgroud)\r\n<div id="clock">\n <button>Remove</button>\n <p></p>\n</div>
Run Code Online (Sandbox Code Playgroud)\r\n当我单击按钮删除时钟时,setInterval
回调仍然被调用。回调闭包强力持有 DOM 节点,这意味着它的资源无法被释放。还有来自按钮事件处理程序的循环引用;尽管也许这个可以由 engine\xe2\x80\x99s 循环收集器处理。话又说回来,也许不是。
不用担心:我可以创建一个辅助函数,确保闭包仅通过弱引用保存 DOM 节点,并抛出FinalizationRegistry
来清理计时器。
const weakCapture = (captures, func) => {\n captures = captures.map(o => new WeakRef(o));\n return (...args) => {\n const objs = [];\n for (const wr of captures) {\n const o = wr.deref();\n if (o === void 0)\n return;\n objs.push(o);\n }\n return func(objs, ...args);\n }\n};\n\nconst finregTimer = new FinalizationRegistry(\n timerId => clearInterval(timerId));\n\n{\n let clockElem = document.getElementById(\'clock\');\n\n const timefmt = new Intl.DateTimeFormat(\n \'default\', { timeStyle: \'medium\', });\n\n const timerId = setInterval(\n weakCapture([clockElem], ([clockElem]) => {\n const d = new Date;\n console.log(\'tick\', d);\n clockElem.querySelector(\'p\').innerHTML =\n timefmt.format(d);\n }), 1000);\n \n finregTimer.register(clockElem, timerId);\n\n clockElem.querySelector(\'button\')\n .addEventListener(\'click\',\n weakCapture([clockElem], ([clockElem], ev) => {\n clockElem.remove();\n }));\n\n clockElem = null;\n\n // now clockElem should be held strongly only by the DOM\n}
Run Code Online (Sandbox Code Playgroud)\r\n<div id="clock">\n <button>Remove</button>\n <p></p>\n</div>\n<button onclick="+\'9\'.repeat(1e9)">Try to force GC</button>
Run Code Online (Sandbox Code Playgroud)\r\n但这似乎不起作用。即使在clockElem
删除节点后, \xe2\x80\x98tick\xe2\x80\x99 仍会继续记录到控制台,这意味着WeakRef
尚未清空,这意味着某些东西似乎仍然持有对 的强引用clockElem
。鉴于 GC 不能保证立即运行,当然,我预计会有一些延迟,但即使当我尝试通过运行像+\'9\'.repeat(1e9)
在控制台中那样占用大量内存的代码来强制 GC 时,弱引用也不会被清除(尽管这足以强制在更琐碎的情况下(例如new WeakRef({})
),GC 和清除弱引用。这种情况在 Chromium (118.0.5993.117) 和 Firefox (115.3.0esr) 中都会发生。
这是浏览器的缺陷吗?或者我是否错过了其他一些强有力的参考?
\n(简而言之:这是在 JavaScript 中实现弱事件模式的尝试。)
\n[Element].remove()
Element
从 DOM 中删除(“断开连接”),但不从内存中删除。只要setInterval
没有结束,它就会继续使用内存中的元素愉快地运行。
因此,结束计时器函数将使该元素成为可垃圾回收的(当然,如果没有其他引用的话)。
您可以使用isConnected属性检查DOM 中元素是否存在,如果不再连接则结束计时器。
对于时钟示例(更改setInterval
为更易于管理setTimeout
)。
document.addEventListener('click', ev =>
ev.target.closest(`#clock`)?.remove());
let timer;
const log = t => document.querySelector(`#log2Screen`).textContent = t;
const clockElem = document.getElementById('clock');
const timefmt = new Intl.DateTimeFormat('default', { timeStyle: 'medium', });
run();
function run() {
if (!clockElem.isConnected) {
console.log(
`div#clock exists in memory:`,
clockElem );
// clear timer when #clock not in DOM
return clearTimeout(timer);
}
const d = timefmt.format(new Date);
log(`tick ${d}`);
clockElem.querySelector('p').textContent = d;
timer = setTimeout(run, 1000);
}
Run Code Online (Sandbox Code Playgroud)
#log2Screen {
color: green;
}
Run Code Online (Sandbox Code Playgroud)
<div id="clock">
<button id="remove">Remove</button>
<p></p>
</div>
<div id="log2Screen"></div>
Run Code Online (Sandbox Code Playgroud)
替代方案 1:您可以在从 DOM 中删除元素时结束计时器(删除时也是如此div#clock
)。
let timer;
document.addEventListener('click', ev => {
ev.target.closest(`#clock`)?.remove();
clearTimeout(timer); // <= clear timer
} );
const log = t => document.querySelector(`#log2Screen`).textContent = t;
const clockElem = document.getElementById('clock');
const timefmt = new Intl.DateTimeFormat('default', { timeStyle: 'medium', });
run();
function run() {
const d = timefmt.format(new Date);
log(`tick ${d}`);
clockElem.querySelector('p').textContent = d;
timer = setTimeout(run, 1000);
}
Run Code Online (Sandbox Code Playgroud)
#log2Screen {
color: green;
}
Run Code Online (Sandbox Code Playgroud)
<div id="clock">
<button id="remove">Remove</button>
<p></p>
</div>
<div id="log2Screen"></div>
Run Code Online (Sandbox Code Playgroud)
#clock
替代方案 2:您可以将元素的分配定位在计时器函数中div#clock
,并且仅在仍连接到 DOM时才继续计时器。
document.addEventListener('click', ev => {
ev.target.closest(`#clock`)?.remove();
} );
let [timer, tick] = [, 0];
const log = t => document.querySelector(`#log2Screen`).textContent = t;
const timefmt = new Intl.DateTimeFormat('default', { timeStyle: 'medium', });
run();
function run() {
const clockEl = document.querySelector('#clock p');
if (clockEl) { // <= only run when #clock in DOM
log(`tick ${++tick}`);
clockEl.textContent = timefmt.format(new Date);
return timer = setTimeout(run, 1000);
}
clearTimeout(timer);
log(`Clock deactived on ${timefmt.format(new Date)}`);
}
Run Code Online (Sandbox Code Playgroud)
#log2Screen {
color: green;
}
Run Code Online (Sandbox Code Playgroud)
<div id="clock">
<button id="remove">Remove</button>
<p></p>
</div>
<div id="log2Screen"></div>
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
456 次 |
最近记录: |