动态加载JS提前执行引发函数未定义报错如何解决
问题成因
分两个阶段对应你碰到的异常:
- 初期动态脚本提前执行
用createElement生成再插入DOM的脚本标签,默认async属性为true,和静态写在HTML里的脚本加载规则完全不一样:- 你同时配置
async=false和defer=true本身就是冲突的。按照HTML规范,这两个属性同时存在时async优先级更高,这就是为什么你一开始看到脚本没等资源下载完成就开始执行,报错时间早于onload回调打印加载完成日志。 - 你贴的
loadScript函数本身还有个未定义变量bug:script.id = elmName里的elmName没有在函数作用域内声明,第一次调用函数时就会抛引用错误,很可能打断后续的属性配置逻辑,导致你设置的async、defer属性根本没生效。
- 你同时配置
- defer生效后仍报
jQuery(...).hover3d is not a function
既然你已经确认脚本加载顺序正常,且hover3d方法定义在报错的同个文件里,这个问题和加载顺序无关,基本是两个原因导致的:- 页面存在多份jQuery实例。如果静态引入了一份jQuery,某个动态加载的脚本又引入/内嵌了另一份jQuery,hover3d插件会注册到它加载时拿到的那份jQuery的原型上,等后面调用方法时,拿到的是另一份没挂载hover3d方法的jQuery实例,直接抛错。
- 插件注册逻辑没有正确挂载到jQuery原型。要么是hover3d定义在局部作用域里没暴露到
jQuery.fn上,要么是代码里调用了jQuery.noConflict(true)释放了全局jQuery控制权,导致插件注册的对象和后续调用的对象不匹配。
解决方案
- 先修复
loadScript的基础逻辑问题。不要同时给动态脚本设置async和defer——defer只对HTML解析阶段就存在的静态脚本有顺序保证,动态插入的脚本设置defer没有稳定的执行顺序保证,要保证脚本按插入顺序执行只需要设置async=false即可。修复后的参考代码:
function loadScript(scriptUrl, elmName = '') { const body = document.getElementsByTagName('body')[0]; const script = document.createElement('script'); if (elmName) script.id = elmName; script.src = scriptUrl; // 仅保留async=false即可保证动态脚本按插入顺序执行 script.async = false; script.onload = () => { console.log("LOADED: " + scriptUrl); } script.onerror = (err) => { console.error("Script load failed: " + scriptUrl, err); } body.appendChild(script); }
- 排查多jQuery实例冲突。打开浏览器控制台,分别执行
jQuery.fn.jquery和$.fn.jquery查看全局jQuery版本,再在hover3d插件注册的位置、以及调用hover3d的位置分别打debug断点,确认两个位置拿到的jQuery版本、实例对象完全一致。如果存在重复加载的jQuery,直接删掉冗余的引入,保证全局只有一份jQuery实例,所有插件都注册到这同一份实例上。 - 修正hover3d插件的注册写法,用标准jQuery插件的IIFE包裹逻辑,保证插件正确挂载到jQuery原型,参考写法:
;(function($) { // 此处$即为传入的全局jQuery实例,避免作用域冲突 $.fn.hover3d = function(customOptions) { // 插件自身逻辑 return this; } })(jQuery);
如果代码里用了jQuery.noConflict(),一定要确认插件包裹的IIFE传入的是正确的jQuery实例,不要出现局部变量覆盖、实例不匹配的问题。
内容的提问来源于stack exchange,提问作者honibis
相关产品推荐
相关产品推荐

