如何保证延迟的JS脚本已经执行?



在我的网站上,我使用javascript模块,根据MDN的说法,这些模块默认是延迟的。我有一些额外的javascript,只有在这些模块加载并执行后才能安全执行。MDN 在这里表示,延迟脚本执行保证在触发DOMContentLoaded事件时已经发生。此外,此处建议您还可以考虑此事件已经触发的情况,如下所示:

function doSomething() {
console.info('DOM loaded');
}
if (document.readyState === 'loading') {  // Loading hasn't finished yet
document.addEventListener('DOMContentLoaded', doSomething);
} else {  // `DOMContentLoaded` has already fired
doSomething();
}

上面的块似乎是我正在寻找的。但是MDN上其他地方的文档让我不确定这是否真的正确。例如,为什么上面的readystate检查查找loading而不是loadinginteractivereadystate的文档说loading后面跟着interactive,并且在interactive状态下"子资源,如脚本,图像,样式表和框架仍在加载

所以在我看来,这里存在不一致。建议的readystate检查不足以保证DOMContentLoaded已触发,或者DOMContentLoaded不足以保证延迟的脚本已完成。

readyState至少为interactive时,这意味着文档已被完全解析,并且源 HTML 中的所有元素现在都存在于 DOM 中。

通常,实现此解决方案的人这样做是因为他们试图将侦听器附加到元素,并且需要等待所有元素存在于 DOM 中 - 因此他们需要做的就是检查document.readyState === 'loading'

如果您还想等待<script type="module">脚本运行,这是一个不同的问题,具有不同的解决方案。

到目前为止,最好的方法是在模块中为您的应用程序设置一个入口点,这样您就不必担心加载顺序 - 它会正常工作。

如果您真的必须确定所有模块脚本何时都从非模块脚本运行(我不建议这样做),则必须迭代它们并侦听它们的load事件。

// run this at the end of the body -
// once all script tags exist, but before they've run
Promise.all(
[...document.querySelectorAll('script[type="module"]')]
.map(script => new Promise(
resolve => script.addEventListener('load', resolve)
))
)
.then(() => {
// all module scripts are loaded
})

但这非常复杂,可能不是一个好方法。

如果您改为侦听窗口的load事件,您还将等待所有其他资源加载(图像和样式表等),这是不希望的。

import "other-module";
// module is guaranteed to be loaded here

通过导入模块,可以保证在模块导入运行之前加载它。

最新更新