如何在没有框架的情况下检查DOM是否准备就绪?

90

这个问题与其他无数类似的问题相似,例如在网站和网络上如何检查DOM是否已经加载完毕。但问题在于:

  • 不能使用像jQuery等框架;
  • 不知道脚本是通过静态放置的<script>标签加载还是在DOM已经加载后的某个时间点通过其他JavaScript加载。

是否可以可靠地、跨浏览器地完成这项任务?

补充说明:我正在编写一个独立的JS文件,可以包含在任意的web页面中。我想在DOM加载完成后执行代码。但我不知道我的脚本将如何被包含。它可能是通过放置一个<script>标记(在这种情况下,传统的onload或DOM准备就绪的解决方案将起作用);或者它可能是通过Ajax或其他方式加载的,这样在DOM已经加载后很长时间才会发生(因此先前提到的解决方案永远不会触发)。


3
document.readyState 属性可以使用,但我不确定它在所有浏览器中的兼容性…… - Digital Plane
1
很抱歉,我不是很好。:( - Vilx-
5
你试过搜索这些内容吗?它们涉及到DOM加载完成后执行JavaScript的方法。以下是链接:
  • http://www.dustindiaz.com/smallest-domready-ever
  • https://gist.github.com/842085
  • https://gist.github.com/841930
  • http://blog.shawndumas.com/cross-browser-domready
  • https://dev59.com/3XM_5IYBdhLWcg3w1G6N
  • https://dev59.com/vXE85IYBdhLWcg3whT9r
- topek
1
看看这个小工具:http://github.com/cms/domready 它大约只有0.5kb的压缩大小。 - Christian C. Salvadó
1
@DigitalPlane - 我收回之前的话。它的浏览器支持非常好!把它作为答案添加进来,我会接受它。 - Vilx-
1
那我会撤回已接受的答案并授予给其他人。 :) - Vilx-
7个回答

111

document.readyState属性可用于检查文档是否准备就绪。根据 MDN 文档,它有以下三种取值:

取值

文档的 readyState 可以是下列取值之一:

  • loading - 文档仍在加载中。
  • interactive - 文档已完成加载并解析,但子资源(如图像、样式表和框架)仍在加载中。
  • complete - 文档及所有子资源均已完成加载。该状态表示 load 事件即将触发。

代码示例:

if(document.readyState === "complete") {
    // Fully loaded!
}
else if(document.readyState === "interactive") {
    // DOM ready! Images, frames, and other subresources are still downloading.
}
else {
    // Loading still in progress.
    // To wait for it to complete, add "DOMContentLoaded" or "load" listeners.

    window.addEventListener("DOMContentLoaded", () => {
        // DOM ready! Images, frames, and other subresources are still downloading.
    });

    window.addEventListener("load", () => {
        // Fully loaded!
    });
}

4
请注意,addEventListener()方法是在IE9中添加的,而Windows XP不支持IE9。 - Christoph
1
当然。这与模拟DOMReady事件的其他解决方案一起使用。 - Vilx-
7
首先,这不是跨浏览器有效的。其次,它也要等待图像加载完成。这就像window.onload一样运作。 - Brian Sweat
5
你应该监听名为 load 的事件而不是 onload,对吗? - natevw
2
[深度技术警告]
  • 我认为使用 document.readyState == "complete" 而不是 document.readyState === "complete" 更加合适。
  • 根据您的代码将要执行的操作,等待所有图像加载完成可能更加合适。
  • 浏览器制造商都如此无能,这难道不让人感到沮丧吗?JavaScript 的设计方方面面都是错误的。
- debater
显示剩余2条评论

10

Firefox、Opera和基于Webkit的浏览器具有文档级别事件 DOMContentLoaded,您可以使用 document.addEventListener("DOMContentLoaded", fn, false) 监听该事件。

在IE中则更加复杂。jQuery在IE中所做的是监视文档对象上的 onreadystatechange 以获取特定的就绪状态,并备用 document.onload 事件。document.onload 事件比DOM就绪事件发生得更晚(仅当所有图像都加载完成时),因此只在较早的事件由于某种原因无法工作时才用作后备。

如果花一些时间搜索,您会发现有代码可以实现此功能。我认为最经过验证的代码位于大型框架如jQuery和YUI中,因此,即使我不使用该框架,我也会查看它们的源代码以了解技术。

下面是 jQuery 1.6.2 中 document.ready() 主要部分的源代码:

bindReady: function() {
    if ( readyList ) {
        return;
    }

    readyList = jQuery._Deferred();

    // Catch cases where $(document).ready() is called after the
    // browser event has already occurred.
    if ( document.readyState === "complete" ) {
        // Handle it asynchronously to allow scripts the opportunity to delay ready
        return setTimeout( jQuery.ready, 1 );
    }

    // Mozilla, Opera and webkit nightlies currently support this event
    if ( document.addEventListener ) {
        // Use the handy event callback
        document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );

        // A fallback to window.onload, that will always work
        window.addEventListener( "load", jQuery.ready, false );

    // If IE event model is used
    } else if ( document.attachEvent ) {
        // ensure firing before onload,
        // maybe late but safe also for iframes
        document.attachEvent( "onreadystatechange", DOMContentLoaded );

        // A fallback to window.onload, that will always work
        window.attachEvent( "onload", jQuery.ready );

        // If IE and not a frame
        // continually check to see if the document is ready
        var toplevel = false;

        try {
            toplevel = window.frameElement == null;
        } catch(e) {}

        if ( document.documentElement.doScroll && toplevel ) {
            doScrollCheck();
        }
    }
},

1
再次强调,如果在DOM已经加载后再附加事件处理程序,这将无法正常工作。 - Vilx-
4
看看我提供的jQuery源代码。这就是为什么他们有一行以“if (document.readyState === 'complete') {”开头的代码,以便处理文档已经准备好的情况。如果你正在构建自己的小型框架来实现这个功能,你需要自己构建这种类型的功能。 - jfriend00

10
如果依赖于document.readyState可行,那么使用轮询的快速和简单解决方案:
(function() {
    var state = document.readyState;
    if(state === 'interactive' || state === 'complete') {
        // do stuff
    }
    else setTimeout(arguments.callee, 100);
})();

document.readyState 是解决我的问题的答案。正如我已经多次提到的,附加到“DOMReady”类型的事件不是问题。问题在于当 readyState==complete 时,DOMReady 事件将永远不会触发。 - Vilx-
@Vilx-:我的观点是,如果您已经依赖于document.readyState(即您不想支持旧版非IE浏览器),则根本不需要任何特定于浏览器的代码(DOMContentLoaded vs. onload vs. onreadystatechangeaddEventListener vs attachEvent)-上述解决方案在任何地方都适用。 - Christoph
据我所知,我唯一不支持的浏览器是3.6以下的FireFox。而且,说实话,这只是一个边缘情况(大多数情况下脚本将以传统方式包含),所以如果我错过了某些非常罕见的组合,也不会有太大的影响。 :P - Vilx-

9

这适用于所有浏览器,简洁明了:

var execute = function () {
  alert("executing code");  
};

if ( !!(window.addEventListener) )
  window.addEventListener("DOMContentLoaded", execute)
else // MSIE
  window.attachEvent("onload", execute)

7

DOMContentLoaded事件在HTML文档完全加载和解析完成时触发,无需等待样式表、图像和子框架完成加载。好消息是,Chrome、Firefox、IE9、Opera和Safari都同样支持它。

document.addEventListener("DOMContentLoaded", function(event) 
{
    console.log("DOM fully loaded and parsed");
}

注意:Internet Explorer 8支持readystatechange事件,可以用于检测DOM何时准备就绪。


3

以下是一种在页面底部运行脚本的方法。此外,通过使用window.onload,您可以等待所有图像/脚本加载完成。或者,您可以简单地将代码放置在底部而不等待图片加载。

<html>
<head>
</head>
<body>
</body>
<script language="text/javascript">
  window.onload = (function (oldOnLoad) {
    return function () {
      if (oldOnLoad) { 
        olOnLoad();  //fire old Onload event that was attached if any.
      }
      // your code to run after images/scripts are loaded
    }
  })(window.onload);

  // your code to run after DOM is loaded
</script>
</html>

编辑:对于Vilx的评论

这里有很多onload绑定,以下是一个例子http://jsfiddle.net/uTF2N/3/


+1。你可能想为那些没有太多经验的人添加一个解释,以便他们在以后搜索并找到这个内容时能够理解它是如何工作的。 - Ken White
1
我认为你违反了:“不知道你的脚本是通过静态放置的<script>标签加载还是在DOM已经加载之后通过其他Javascript加载”。 - Levi Morrison
对不起,我之前没有说清楚我是在谈论一个独立的脚本。我已经澄清了。 - Vilx-
在我的情况下,我甚至不会过多关注,但即使如此,有很多DOM就绪脚本可用,所以这不是问题。主要问题是我不知道onload事件是否已经早已触发,因此附加另一个处理程序将没有任何效果。 - Vilx-
不,不,不,你没有理解!如果 onload 早在5分钟前就已经触发了,然后我的脚本才来尝试这个方法呢?它的代码将永远不会被执行! - Vilx-
显示剩余3条评论

1
我们可以使用纯 JavaScript 方法DOMContentLoaded

const domReady = (callBack) => {
  document.addEventListener('DOMContentLoaded', callBack);
  if( document.readyState === "interactive" || document.readyState === "complete" ) {
    callBack();
  }
}

const WhenDomLoaded = () => {
  console.log("Dom Loaded now!")
}

domReady(() => WhenDomLoaded() );


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