如何检测DOMContentLoaded事件是否被触发

80

我正在尝试开发一个库,并且在其中尝试处理页面加载。
在这个过程中,我想使该库完全兼容使用defer和async。

我想要的很简单:
如何知道DOM已经加载完成并被执行文件调用?

为什么这么难呢?
在IE中,document.readyState在DOMContentLoaded之前显示interactive。
不会以任何方式使用浏览器检测,这违反了我的政策和其他参与者的政策。

什么是正确的替代方案?

编辑:

看起来我没有表达得清楚。 我想知道load事件是否已经发生! 我已经知道如何解决那个问题! 我想知道如何处理DOMContentLoaded!!!


设置一个监听器来设置属性或变量。如果被设置,事件已经被分派。当然,你可能在一个不支持该事件的浏览器中,这种情况下它永远不会发生。 - RobG
@RobG 我已经知道了对于不支持DOMContentLoaded的浏览器的答案。对于这些浏览器,我使用load事件和onload事件。但是我仍然没有得到我提出的问题的答案。 - brunoais
没有"onload"事件,但有一个onload属性/属性用于设置load事件的监听器。如果您想明确知道是否发生了DOMContentLoaded,请设置一个监听器并查看是否已调用它。 - RobG
6个回答

83

检查页面中的所有资源是否已加载:

if (document.readyState === "complete" || document.readyState === "loaded") {
     // document is already ready to go
}

这一特性在IE和Webkit中已经有很长时间的支持。在Firefox 3.6中添加了该特性。这里是规范"loaded"适用于早期的Safari浏览器。

如果你想知道页面何时被加载和解析,但所有的子资源尚未加载(这更类似于DOMContentLoaded),你可以添加"value"属性为"interactive":

if (document.readyState === "complete" 
     || document.readyState === "loaded" 
     || document.readyState === "interactive") {
     // document has at least been parsed
}

除此之外,如果你真的只想知道 DOMContentLoaded 何时触发,那么你需要在它触发之前安装一个事件处理程序,并在其触发时设置一个标志。

这篇 MDN 文档也是一个非常好的阅读材料,可以更好地理解 DOM 状态。


4
你没有回答问题。我想知道DOMContentLoaded事件,而不是load事件。load事件很容易理解,但我不知道DOMContentLoaded。 - brunoais
2
@brunoais - 然后安装一个 DOMContentLoaded 事件的处理程序,并在其触发时设置一个标志。我认为没有其他选择。 - jfriend00
1
如果您想在文档解析完成之前,但子资源仍在加载时包含时间,您也可以检查 "interactive"。请参见我在答案中添加的第二个示例。 - jfriend00
只是为了给这个讨论增添一些乐趣 http://ablogaboutcode.com/2011/06/14/how-javascript-loading-works-domcontentloaded-and-onload/ - Jayapal Chandran
5
第一段代码片段有误 - document.readyState == "interactive" 也可以表示 DOMContentLoaded 已触发。请参考这里:https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState - thedayturns
显示剩余3条评论

25

您可以检查文档的readyState值,并以此确定事件是否已触发。以下是在文档完成解析时运行名为start()的函数的代码:

if (/complete|interactive|loaded/.test(document.readyState)) {
    // In case the document has finished parsing, document's readyState will
    // be one of "complete", "interactive" or (non-standard) "loaded".
    start();
} else {
    // The document is not ready yet, so wait for the DOMContentLoaded event
    document.addEventListener('DOMContentLoaded', start, false);
}

注意上面的代码检测到 文档解析完成 时。要注意这不同于检测是否已触发 DOMContentLoaded(该事件会在 interactive 之后立即发生),但它具有相同的实际目的,即告诉您文档已经完成加载并解析,但子资源(如图像、样式表和框架)仍在加载中 (来源)。


2
这是不正确的。DOMContentLoaded仅在interactive之后触发(证明在此处:https://jsfiddle.net/luciopaiva/grs82byv/20/) - Lucio Paiva
@LucioPaiva 谢谢,你说得对 - 但这只意味着关于 DCL 已经触发的评论是不正确的。代码在所有情况下仍然完美运行,那么为什么要减1? - oriadam
1
你说得对,我被那里的评论误导了。我编辑了你的答案并修正了它,并增加了一些信息;请让我知道你的想法。 - Lucio Paiva
1
如果有选择的话,你应该总是更喜欢可读性更高的代码版本。"A more readable regexp would be [...]" - Denilson Sá Maia
@DenilsonSáMaia 我同意。已更改。 - oriadam
@oriadam 你有没有想法为什么它在谷歌文档上不起作用? - JinSnow

18

如何在DOMContentLoaded(或ASAP)上正确运行某些内容

如果您需要等待HTML文档完全加载和解析才能运行某些内容,则需要等待DOMContentLoaded,毫无疑问。但是,如果您无法控制脚本何时执行,那么当您有机会监听事件时,DOMContentLoaded可能已经被触发。

为考虑到这一点,您的代码需要检查DOMContentLoaded是否已经被触发,如果是,则立即执行需要等待DOMContentLoaded的任何内容:

function runWhenPageIsFullyParsed() {
    // your logic goes here
}

if (document.readyState === "complete") {
    // already fired, so run logic right away
    runWhenPageIsFullyParsed();
} else {
    // not fired yet, so let's listen for the event
    window.addEventListener("DOMContentLoaded", runWhenPageIsFullyParsed);
}

正确的事件顺序为:

  • document.readyStateloading 开始
  • document.readyState 变为 interactive
  • windowDOMContentLoaded 事件被触发
  • document.readyState 变为 complete
  • windowload 事件被触发

参考资料:MDN

您可以在 this fiddle 中查看页面加载期间完整的事件顺序。


2
在你的 JS 代码中添加 log(document.readyState); 可以帮助指出 loading 状态,该状态发生在交互之前。我建议将这个重要步骤添加到事件顺序中。在 MDN readyState 文章中有详细介绍。谢谢! - King Holly
我尝试了上面的解决方案,但仍有几次我的代码无法运行。因此,我最终在我的代码中添加了document.readyState === "interactive"条件。 - Saurabh Gupta
1
@KingHolly,我已经采纳了您的建议并更新了答案,谢谢。 - Lucio Paiva
@SaurabhGupta 这很有趣。你能展示一些能够重现这个问题的代码吗?我尝试在我的上面的小工具中重新运行了代码,但每次都可以正常工作。 - Lucio Paiva
@LucioPaiva 我正在 $.ajax.done 方法中运行它。此外,这段代码正在运行在一个 OSGI 容器中,我的代码被安装到其中(Atlassian 插件)。因此,加载顺序不由我控制,并且同时加载了许多不同的项目。 - Saurabh Gupta

1

尝试这个或查看这个link

<script>
    function addListener(obj, eventName, listener) { //function to add event
        if (obj.addEventListener) {
            obj.addEventListener(eventName, listener, false);
        } else {
            obj.attachEvent("on" + eventName, listener);
        }
    }

    addListener(document, "DOMContentLoaded", finishedDCL); //add event DOMContentLoaded

    function finishedDCL() {
        alert("Now DOMContentLoaded is complete!");
    }
</script>

注意

如果在 <link rel="stylesheet" ...> 后面有一个 <script>,页面将无法完成解析 - 并且 DOMContentLoaded 不会触发 - 直到样式表加载完毕。


2
不错的尝试,但那并不是我真正想要的。那并没有检查事件是否已经被触发,它只检查事件是否正在被触发。我知道如何检查事件是否正在被触发,但我不知道如何检查事件是否已经被触发。 - brunoais

0
如果你想要“确切地”知道 DOMContentLoaded 是否被触发,你可以使用像这样的布尔标志:
var loaded=false;
document.addEventListener('DOMContentLoaded',function(){
loaded=true;
...
}

然后使用以下代码进行检查:

if(loaded) ...

1
不错的尝试:)。虽然那不是一个真正的答案。因为我希望它能在所有(主要)浏览器中正确运行,并且支持异步和延迟加载,所以我不能确定它会如预期般在IE上工作。 - brunoais
4
IE不是一款浏览器:D - deviato

-7

事情是这样的,如果其他库(如jQuery)已经使用DOMContentLoaded,那么您就不能再次使用它。这可能是个坏消息,因为最终您将无法使用它。你会说,为什么不只是使用$(document).ready(),因为不是所有东西都需要使用jQuery,特别是如果它是一个不同的库。


3
你可以拥有多个事件监听器,这并不是不可能的。请阅读以下链接了解更多信息: https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener https://msdn.microsoft.com/zh-cn/library/ff649664.aspx(订阅/发布模式) - dotnetCarpenter

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