动态添加脚本onload回调找不到全局函数,求除setTimeout外的解决方案
问题描述
步骤1:加载外部脚本,将onload设置为调用window.initControllers:
(function(document, tag) { var script = document.createElement(tag), el = document.getElementsByTagName(tag)[0]; script.src = 'https://unpkg.com/@hotwired/stimulus@3.1.0/dist/stimulus.umd.js'; script.onload = function () { window.initControllers(); }; el.parentNode.insertBefore(script, el); }(document, 'script'));
步骤2:在另一个动态添加的脚本中定义window.initControllers函数:
(function(document, tag) { var script = document.createElement(tag), el = document.getElementsByTagName(tag)[0]; script.type = 'module'; var contents = "var initControllers = function () { console.log('initControllers'); };", contents += 'window.initControllers = initControllers;'; script.appendChild(document.createTextNode(contents)); el.parentNode.insertBefore(script, el); }(document, 'script'));
报错情况
部分页面刷新时出现以下错误:
Uncaught TypeError: window.initControllers is not a function.
提问
除了使用setTimeout相关的复杂方法外,有哪些方案可以确保步骤1的脚本加载时,window.initControllers已被定义?
解决方案
调整脚本加载顺序
将步骤2的脚本插入逻辑放在步骤1之前执行。当前两个脚本都是异步插入,且模块脚本(type="module")默认延迟执行,可能导致步骤1的外部脚本先加载完成,此时initControllers还未挂载到window。先插入定义函数的脚本,再加载外部脚本,可保证函数先被定义。移除模块脚本类型
模块脚本会在所有同步脚本执行完毕后才运行,而非模块脚本会立即执行。删除步骤2中的script.type = 'module';,插入的脚本会同步执行,window.initControllers会被立即定义,避免未定义问题。将外部脚本加载逻辑嵌套到函数定义脚本中
在步骤2的脚本内容里,定义完initControllers后再动态加载步骤1的外部脚本,从根源上保证依赖顺序:(function(document, tag) { var script = document.createElement(tag), el = document.getElementsByTagName(tag)[0]; script.type = 'module'; var contents = ` var initControllers = function () { console.log('initControllers'); }; window.initControllers = initControllers; // 加载外部脚本 const stimScript = document.createElement('script'); stimScript.src = 'https://unpkg.com/@hotwired/stimulus@3.1.0/dist/stimulus.umd.js'; stimScript.onload = () => window.initControllers(); document.head.appendChild(stimScript); `; script.appendChild(document.createTextNode(contents)); el.parentNode.insertBefore(script, el); }(document, 'script'));使用自定义事件实现依赖通知
在步骤2中定义完函数后触发自定义事件,步骤1的外部脚本加载完成后,先检查函数是否存在,不存在则监听事件,确保函数就绪后再调用:
步骤2修改后代码:(function(document, tag) { var script = document.createElement(tag), el = document.getElementsByTagName(tag)[0]; script.type = 'module'; var contents = ` var initControllers = function () { console.log('initControllers'); }; window.initControllers = initControllers; // 触发就绪事件 window.dispatchEvent(new CustomEvent('initControllersReady')); `; script.appendChild(document.createTextNode(contents)); el.parentNode.insertBefore(script, el); }(document, 'script'));步骤1修改后代码:
(function(document, tag) { var script = document.createElement(tag), el = document.getElementsByTagName(tag)[0]; script.src = 'https://unpkg.com/@hotwired/stimulus@3.1.0/dist/stimulus.umd.js'; script.onload = function () { if (typeof window.initControllers === 'function') { window.initControllers(); } else { window.addEventListener('initControllersReady', () => { window.initControllers(); }, { once: true }); } }; el.parentNode.insertBefore(script, el); }(document, 'script'));
内容的提问来源于stack exchange,提问作者gremo
相关产品推荐
相关产品推荐

