你能否在不使用document.write()的情况下销毁文档元素和事件监听器?

5
在下面的示例中:
  • 如果您右键单击文档,它会告诉您它正在侦听。
  • 如果您单击m1,它将替换文档元素,但右键单击文档仍会通知您它正在侦听。 您必须在顶部附近右键单击,因为文档没有内容。
  • 如果您单击m2,它将覆盖文档内容,并在顶部附近右键单击不再起作用。 在开发工具中检查文档可以验证事件处理程序已经消失。
  • 按下一个按钮后,必须再次运行代码片段以尝试下一个,因为此演示是破坏性的。

有没有一种不使用document.write()函数的方法,在销毁文档并用新文档替换时,以使事件处理程序被销毁?

document.write()易出错,其使用方式"强烈不建议",但我仍希望能够销毁文档及其事件监听器。

document.addEventListener('contextmenu', function (e) {
  alert('still listening');
  e.preventDefault();
})

function m1() {
  var doc = document.implementation.createHTMLDocument();
  document.replaceChild(
    document.importNode(doc.documentElement, true),
    document.documentElement
  );
}
function m2() {
  document.close();
  document.write('<html></html>');
}
<button onclick="m1()">m1</button>
<button onclick="m2()">m2</button>

为了清楚起见,“m1”按钮/函数未达到我的目标,因为尽管文档元素被替换了,但是上一个文档元素的事件处理程序由于某种原因被保留了下来。我想要实现与“m2”相同的效果,但是不使用推荐避免使用的document.writedocument.close
补充说明:
这个问题严格来说是为了更好地理解语言和实现它们的引擎的限制。请不要试图读出其他意思或解决其他目标。这只是一个关于是否可能的问题。
我不需要知道如何删除事件侦听器或管理事件侦听器,也不需要知道如何删除文档的所有子元素。我想知道是否可能销毁文档元素本身,不留下任何<html>标记,并且不会留下任何事件侦听器。使用document.write()可以实现这一点,但我只是想知道是否有其他方式可以实现这个确切的目标,而不是其他假定的目标。

1
window.reload() 会销毁文档。 - Taplar
2
document.write通常不建议使用,因为对于新手来说很容易陷入困惑的陷阱。但是,只要你有足够的知识和小心谨慎,它也有其适用情况 - 这可能就是其中之一。 - CertainPerformance
2
另一件需要记住的事情是 - 监听器甚至可以添加到高于文档的window - CertainPerformance
document.create怎么样?删除文档并使用document.create创建它。 - user13806962
3
我有一种隐秘的怀疑,这是一个 XY 问题。无论如何,除非您添加一个钩子并在任何注册之前跟踪对 document.addEventListener 的调用,否则您无法删除事件侦听器。document.open() 立即跟随 document.close() 将会移除所有文档事件侦听器,但是不会取消挂起的 promises,setIntervalsetTimeout;但是,您可以使用从 0setInterval(undefined)clearInterval 取消间隔。如果需要,我可以制作演示文稿。 - concision
显示剩余19条评论
3个回答

2

我刚刚意识到,实际上你可以这样做,并且第一个例子m1确实替换了documentElement并删除了它的事件监听器。有趣的是,它在一开始并没有任何事件监听器,它们在文档本身而不是文档元素上。开发者工具(在FireFox中)会欺骗您,显示它们附加到<html>元素上,即使它们在技术上并未附加到元素上。如果您将示例修改为实际将事件附加到document.documentElement而不是文档本身,则单击m1时该事件将被禁用:

document.documentElement.addEventListener('contextmenu', function (e) {
  alert('still listening');
  e.preventDefault();
})

function m1() {
  var doc = document.implementation.createHTMLDocument();
  document.replaceChild(
    document.importNode(doc.documentElement, true),
    document.documentElement
  );
}
function m2() {
  document.close();
  document.write('<html></html>');
}
<button onclick="m1()">m1</button>
<button onclick="m2()">m2</button>

最初我想说不行,就像用户Livingston Samuel在这个类似但不完全相同的问题中所说的那样:"您无法将当前文档对象或任何文档对象替换为使用createHTMLDocument方法创建的Document对象。"。

然而,坚持我的原始问题,我并不是在问document,而是在问documentElement。因此,鉴于上面修改后的代码,我会说可以

有趣的是,在Firefox中,无论事件侦听器是附加到document对象还是documentElement对象,从版本81开始都会显示在html元素上。我可能会期望,当它们附加到document时,它们根本不会显示出来,或者将它们放在某个抽象对象上。一个iframe确实有一个标记为#document的抽象对象可用,但它没有事件标签:

Document object in FireFox dev tools.

有趣的是,document.write()实际上破坏了document对象上的事件,而不仅仅是document.documentElement。它可能在这里的某个地方有记录,但我找不到了。

在测试Chrome后,我注意到开发人员工具区分事件是附加到documentElement:

documentElement contextmenu listener

还是文档本身:

document contextmenu listener


0

我不确定这个问题背后的原因,但我可以猜测,您在页面中重新创建对象和事件时遇到了问题。

如果是这种情况,正如MDN所示(https://developer.mozilla.org/en-US/docs/Web/API/Node/removeChild),尽管您从HTML中删除了元素,它们仍然可能在内存中存在一段时间,等待垃圾收集器清理它们。

因此,如果您希望安全地删除包含事件的代码片段,则最安全的方法是首先使用您喜欢的库(如jQuery)或使用本机JavaScript通过DOM删除事件: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener

一旦您删除了所有事件,那么您可以删除所有元素,从内存角度来看,最安全的方法是通过循环,先删除内部元素,然后再删除父元素,以此类推。


-1
您可以这样使用document.getElementById('id-here').innerHTML = ""

<html id="the-website">

<body>
  <input type="button" onclick="document.getElementById('the-website').innerHTML = '';" value="Don't click me plz!">
</body>

</html>


outerHTML()怎么样? - Terry Riegel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接