如何知道窗口的“load”事件是否已经被触发

90

我正在编写一段JavaScript脚本。 这个脚本可能会异步加载(AMD格式)。

在这个脚本中,我想要等到触发 window.load 事件之前不做任何重要的事情。 所以我监听窗口的 "load" 事件。

但是如果脚本在 window.load 事件之后加载……我怎么知道 window.load 已经被触发了呢?

当然,我不想在任何其他脚本中添加任何东西(它们都是异步加载的,问题也是一样的):)

编辑:

想象一下一个完全没有JavaScript的HTML文档。

然后有人在这个文档中插入一个 script 标签,并且这个 script 标签加载了我的 JavaScript 文件。

这将执行我的脚本。

如何知道此脚本是否已经触发了 window.load 事件呢?

没有jQuery,没有任何在我的脚本之前的HTML文档中的脚本。

有可能知道吗?

我发现了 window.document.readystate 属性。我猜这个属性是用于文档 "ready" 事件的,而不是用于 window "load" 事件。 是否有类似于 window "load" 事件的东西?


为什么不使用jQuery的文档就绪事件?否则,挂接window.load事件并设置一个标志(例如window.loadFired),并排队超时以每50ms检查一次该标志。 - Nikola Radosavljević
你能否在窗口加载事件中设置一个全局布尔值,而不是异步加载吗? - Xitalogy
2
我想等待窗口“load”事件,因为在此事件之前,浏览器仍然很忙。我想在浏览器真正准备好时开始工作…也许这很愚蠢,但无论如何,无法知道事件是否已被触发仍然是一个问题…不是吗? - Nicolas
3
根据我的理解,@nicolas在询问"document.readyState"是否包含字符串"complete",当文档的所有元素都已经加载完毕时。如果我的理解正确,可以参考这个链接:https://developer.mozilla.org/en-US/docs/DOM/document.readyState。此外,这篇MSDN文章似乎也表明它可能是你所需要的:http://msdn.microsoft.com/en-us/library/ie/ms534359(v=vs.85).aspx。 - Xitalogy
我在使用书签脚本时遇到了困境。 - gcb
显示剩余2条评论
7个回答

70

13
来自 MDN 的相同解决方案 https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState#Values - lizlux
6
根据MDN的说法,当load事件即将触发时,document.readyState会变为'complete',因此存在竞争条件的可能性,即您的代码将在load事件之前运行。如果您绝对需要在load事件后运行代码(但不知道load事件是否已触发),则可能运气不佳。 - Ted Phillips
@Ted Phillips:也许设置一个小的超时时间会更好,这样测试就会在下一个事件循环中发生。 - Panu Logic

49

快速回答

为了快速回答问题的标题:

document.readyState === 'complete'

更深入的例子

如果你想在窗口加载时调用代码,同时还要处理窗口可能在你的代码运行时已经加载好的情况,下面是一个很好的助手。

function winLoad(callback) {
  if (document.readyState === 'complete') {
    callback();
  } else {
    window.addEventListener("load", callback);
  }
}

winLoad(function() {
  console.log('Window is loaded');
});

注意: 这里的代码片段实际上不在同一个窗口环境中运行,所以当您运行此代码时,document.readyState === 'complete' 实际上会计算为 false。如果您现在将相同内容输入控制台,则应该计算为 true。

另请参阅: 非 jQuery 的 '$(document).ready()' 的等价物是什么?


@IgorBykov 在评论中提出的边缘情况处理

Igor 在评论中提出了一个有趣的问题,下面的代码可以尝试进行最佳努力超时处理。

问题在于,在 load 事件触发之前,document.readyState 可能已经是 complete。我不确定这可能会导致什么潜在问题。

一些关于流程和事件触发的文档资料

Complete: state 表示 load 事件即将被触发。

提供了一个事件触发的实时示例:

  1. readyState: interactive
  2. 事件触发: DOMContentLoaded
  3. readyState: complete
  4. 事件触发: load

load 触发之前,readyState 可能会短暂地为 complete。我不确定在此期间可能遇到什么问题。

下面的代码注册了 load 事件监听器,并设置超时以检查 readyState。默认情况下,在检查 readyState 之前,它将等待 200 毫秒。如果 load 事件在超时之前触发,我们一定要确保不再次触发回调函数。如果我们到达超时结束时,仍未触发 load,我们将检查 readyState 并确保避免在稍后可能仍然触发 load 的情况。

根据你想要达到的目标,你可能希望无论如何都运行load回调(删除if (!called) {检查)。在回调函数中,你可能希望在潜在的代码中加入try/catch语句,或者检查是否有可以限制执行的东西,以便在两次调用时,只有当期望的所有内容都可用时才执行工作。

function winLoad(callback, timeout = 200) {
  let called = false;

  window.addEventListener("load", () => {
    if (!called) {
      called = true;
      callback();
    }
  });

  setTimeout(() => {
    if (!called && document.readyState === 'complete') {
      called = true;
      callback();
    }
  }, timeout);
}

winLoad(function() {
  console.log('Window is loaded');
});


@IgorBykov 这就是为什么有else块来添加一个事件监听器load,否则,除非你谈论的是我误解的不同情况。啊,你是说加载事件还没有发生。阅读后:https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState 它声明:Complete: The state indicates that the load event is about to fire. - CTS_AE
无条件的“load”监听器也无法解决问题,不幸的是因为有一个完美的机会它可能根本不会触发(例如,您已经附加得太晚了)。轮询也不是一个选项,因为您无法轮询“load”状态本身。 - Igor Bykov
可能,一个不错的选择是无条件地附加load监听器,等待一段时间(例如200毫秒),如果监听器尚未执行并且readyStatecomplete,则我们知道我们已经在load之后执行。这将是一种粗略但相对可靠的方式。 - Igor Bykov
@IgorBykov 我添加了一些代码,希望能处理您的边缘情况。我仍然不确定在readyState: completeload事件触发之间,您的状态会出现什么问题。我还提到了如何通过轻微的代码更改可能多次调用回调函数。我相信您已经处理了奇怪的情况,但我认为如果其他人遇到了您的问题,我会添加一些代码来帮助处理它。我不确定您是否只是找到一个动态元素,直到完全加载后才存在? - CTS_AE
嘿,谢谢!在我的情况下,我需要在iframe准备好接收消息后postMessage。 问题是,如果我在load之前这样做(即使是readyState ==='complete'),接收代码仍未监听该消息。 在我的特定情况下,轮询是可能的(因为接收端可以响应并确认已接收到消息),这正是我最终做出的(在某种程度上类似于您的解决方案)-感谢您调整答案 :) - Igor Bykov
显示剩余3条评论

14

浏览器导航性能的loadEventEnd指标可用于确定是否已触发加载事件:

let navData = window.performance.getEntriesByType("navigation");
if (navData.length > 0 && navData[0].loadEventEnd > 0)
{
    console.log('Document is loaded');
} else {
    console.log('Document is not loaded');
}

7
如果(performance.timing.loadEventEnd){ /* 窗口已加载 */ } - Blaise
6
performance.timing被弃用,因此尽管它可能更简单,但使用它是有风险的。 - MynockSpit
5
针对未被弃用版本的情况,翻译内容为:performance.getEntriesByType("navigation").every((e) => e.loadEventEnd) // true|false。该代码用于判断所有导航性能条目的loadEventEnd属性是否存在,并返回一个布尔值truefalse - Blaise

0

基于@CTS_AE的方法,我为以下环境提供了一个解决方案:

  • window.addEventListener('load', activateMyFunction);
  • window.addEventListener('DOMContentLoaded', activateMyFunction);

都无法工作。

它需要进行单个字符替换(例如从

window.addEventListener('load', activateMyFunction);

window_addEventListener('load', activateMyFunction);)

函数 window_addEventListener() 的样子是这样的:

const window_addEventListener = (eventName, callback, useCapture = false) => {

  if ((document.readyState === 'interactive') || (document.readyState === 'complete'))   {

    callback();
  }
}

-1

简单方法:

window.onload = (event) => {
  console.log('page is fully loaded');
};

你可以从资源这里找到其他方法。


-1
如果您不想使用jQuery,它所使用的逻辑是:
if( !document.body )
    setTimeout( checkAgain, 1 );

因此,在窗口加载事件和检查文档的body属性是否可用之间,您可以检查DOM是否已准备就绪。


3
当DOM准备就绪时,document.body可用。而当触发窗口“load”事件时,它不可用。这个解决方案是用于检测文档“ready”(DOM准备就绪)的,但我更喜欢猜测窗口“load”事件... - Nicolas
1
一个应该尽量避免使用setTimeout轮询的方法,特别是当存在事件可用时。例如:window.addEventListener("load", callback) - CTS_AE

-4

覆盖window.load怎么样?

window._load = window.load;
window.load = function(){
  window.loaded = true;
  window._load();
}

然后检查

if (window.loaded != undefined && window.loaded == true){
    //do stuff
}

2
这样做是不行的。如果窗口“加载事件”已经被触发,覆盖window.load是无用的,它已经被执行过了,不会再次执行... - Nicolas

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