使用setTimeout拆分代码后iframe中load事件无法触发的问题排查
问题背景与现象
- 内部JS框架按不同生命周期(
INIT.IMMEDIATE、INIT.DOM_CONTENT_LOADED、INIT.PAGE_LOAD)收集控件初始化函数,为拆分主线程长任务、提升UI响应性,用setTimeout(func, 0)包裹每个初始化任务延迟执行。 - 主窗口中该方案运行正常,但在同源iframe弹窗页面里,通过
INIT.IMMEDIATE类型初始化函数绑定的window.load事件无法触发,移除setTimeout后功能完全恢复正常。
相关代码片段
控件初始化代码
_framework.init(NAME, INIT.IMMEDIATE, function () { let loader = document.getElementById(ID_LOADER); if (loader.classList.contains(CSS_SHOW_BY_DEFAULT)) { window.addEventListener("load", hide); // 主窗口触发,iframe中不触发 } });
框架收集初始化函数逻辑
_framework.init = function (name, pageLifeCycle, init) { if (!name || !pageLifeCycle || !init) { throw new Error("name, page life cycle time and init function must be defined"); } let initialiser = { [name]: init }; switch (pageLifeCycle) { case INIT.IMMEDIATE: initialisersImmediate = { ...initialiser, ...initialisersImmediate }; break; case INIT.DOM_CONTENT_LOADED: initialisersDOMContentLoaded = { ...initialiser, ...initialisersDOMContentLoaded }; break; case INIT.PAGE_LOAD: initialisersPageLoad = { ...initialiser, ...initialisersPageLoad }; break; default: throw new Error("invalid init execution time specified"); } };
框架执行初始化逻辑
function _initialisers(obj) { for (const [key, func] of Object.entries(obj)) { setTimeout(func, 0); } }; _initialisers(initialisersImmediate); window.addEventListener("DOMContentLoaded", function domContentLoaded() { _initialisers(initialisersDOMContentLoaded); }); window.addEventListener("load", function pageLoad() { _initialisers(initialisersPageLoad); });
问题原因分析
- 浏览器的
load事件是一次性触发事件,仅在页面所有资源(DOM、样式、图片、脚本等)全部加载完成时触发一次,触发后不会再重复触发。 - iframe页面资源加载速度通常快于主窗口,当框架执行
_initialisers(initialisersImmediate)时,setTimeout(func, 0)会把INIT.IMMEDIATE的初始化函数推入宏任务队列,等待当前同步代码执行完成后再执行。但此时iframe的页面加载流程可能已经完成,load事件已经触发结束。 - 当setTimeout的回调函数执行、去绑定
load事件时,事件早已触发过,绑定的hide函数自然不会被调用。 - 主窗口因资源量大、加载耗时久,
load事件触发时机晚于setTimeout回调的执行时机,所以能正常完成事件绑定并触发。而移除setTimeout后,INIT.IMMEDIATE的初始化函数会同步执行,在load事件触发前就完成了绑定,因此功能正常。
内容的提问来源于stack exchange,提问作者Chris Spittles
相关产品推荐
相关产品推荐

