如何正确重新派发DOMContentLoaded、load等页面原生加载事件
WordPress脚本延迟加载工具事件派发失效问题
问题背景
我正在开发一款适用于WordPress的脚本延迟加载工具,核心逻辑是:
- 初始加载时将所有待延迟脚本的
src属性设置为null - 待页面发生用户交互后,再将
src还原为原始目标地址加载脚本
目前逻辑整体运行正常,但部分脚本依赖load、DOMContentLoaded这类原生加载事件触发执行,因此需要在延迟脚本加载完成后重新派发这类事件,保证脚本正常运行。
已尝试方案与异常现象
我尝试手动构造事件对象调用dispatchEvent派发事件,代码如下:
window.document.dispatchEvent(new Event("DOMContentLoaded", { bubbles: true, cancelable: true }));
该方案未生效,依赖事件的脚本没有做出响应。
我编写的脚本加载与事件派发逻辑如下,Promise链可以正常执行完成,控制台会打印success日志:
loadScripts( makeGraph(document.querySelectorAll("script[data-type=lazy]"))) .then(window.dispatchEvent(new Event('DOMContentLoaded', { bubbles: true, cancelable: true }))) .then(window.dispatchEvent(new Event('load'))) .then(console.log('success')) .catch(console.error);
我编写了测试脚本验证事件监听是否生效:
window.addEventListener( 'load', () => { alert('I loaded!'); }) alert('I loaded 2'); window.addEventListener( 'DOMContentLoaded', () => { alert('DOM Content loaded!'); });
测试时仅弹出I loaded 2提示,说明设置src属性后脚本确实可以正常加载,但事件监听没有被触发。如果移除延迟加载脚本,让WordPress按默认逻辑加载脚本,对应的eventListener可以正常触发。
我已经尝试过分别在window和window.document对象上调用dispatchEvent派发事件,二者效果没有差异,该问题在Chrome、Safari浏览器下均存在。
问题根因
两个核心问题导致事件派发失效:
- Promise链写法错误:
.then()需要传入回调函数,现有写法在Promise初始化时就同步执行了dispatchEvent,事件派发时机远早于延迟脚本的加载、执行时间,此时脚本内的事件监听器还未注册,自然无法捕获事件。 - 原生页面加载事件的特殊机制:
DOMContentLoaded和load是浏览器级别的一次性事件,首次触发后浏览器会标记文档加载状态(document.readyState从loading变为interactive再变为complete),后续注册的事件监听器如果检测到状态已经满足,会直接执行回调,不会等待后续手动派发的同名事件。手动派发事件时,原生事件早已触发完成,document.readyState已经是complete状态,延迟脚本内注册的监听器不会响应后续派发的事件。
可行修复方案
按以下步骤调整代码即可解决问题:
- 修正Promise链的执行时机,所有事件派发逻辑必须包裹在
.then()的回调函数中,确保所有延迟脚本加载、执行完成后再运行派发逻辑。 - 派发事件前同步修改
document.readyState的值,兼容所有先检查加载状态再绑定监听器的脚本。 - 针对jQuery这类会自行缓存加载就绪状态的通用库,单独修正其内部就绪标记。
完整修复代码参考:
loadScripts(makeGraph(document.querySelectorAll("script[data-type=lazy]"))) .then(() => { // 先将就绪状态改回interactive,对应DOMContentLoaded触发前的状态 Object.defineProperty(document, 'readyState', { get: () => 'interactive', configurable: true }) // 在document对象上派发DOMContentLoaded事件 document.dispatchEvent(new Event("DOMContentLoaded", { bubbles: true, cancelable: true })); // 再将就绪状态改为complete,对应load事件触发后的状态 Object.defineProperty(document, 'readyState', { get: () => 'complete', configurable: true }) // 在window对象上派发load事件 window.dispatchEvent(new Event('load', { bubbles: false, cancelable: false })); // 兼容jQuery的就绪状态 if (window.jQuery) { window.jQuery.isReady = false; window.jQuery.ready(); } }) .then(() => console.log('success')) .catch(console.error);
额外优化建议:还原脚本src属性时,给对应script标签设置async=false,保证脚本执行顺序和页面原始定义的顺序一致,避免依赖前置脚本的逻辑报错。
内容的提问来源于stack exchange,提问作者philolegein
相关产品推荐
相关产品推荐

