JavaScript动态加载优化:异步队列与Ready机制实现
本文深入探讨JavaScript动态加载JS模块的异步策略优化。通过监听DOMContentLoaded事件实现页面Ready后的加载,利用asynQueue数组构建函数执行队列,并采用迭代而非循环的方式处理异步回调,避免模块重复加载与执行冲突。文章提供了完整的UsingJS库代码示例及注意事项,确保模块加载的稳定性与顺序可控。
动态加载策略概述
前两篇文章已详细阐述了通过动态加载JS文件或JS模块逐步实现的过程。
首先,文章介绍了通过同步策略实现模块加载与回调函数的分离;随后,进一步通过异步策略实现了同样的分离目标。
本篇重点在于优化异步策略,实现非任意顺序的模块随意加载,并在页面Ready之后加载文件。我们将接着解决上一篇遗留的问题。
第一个问题是:在页面Ready之后进行加载。
第二个问题是:如何随意添加模块并进行加载。
页面Ready后加载机制
先看第一个问题,这相对简单,主要涉及监听页面的DOMContentLoaded事件。网络上已有大量相关解答,此处直接展示代码实现。
代码如下:
Using.ready = function(callback){
readyList.push(callback);
if(document.addEventListener){
document.addEventListener("DOMContentLoaded",_ready,false);
return;
}
// for IE
var domReady = function(){
try{
document.documentElement.doScroll("left");
_ready();
}catch(ex){
setTimeout(domReady,1);
return;
}
}
domReady();
} 这段代码中,最晦涩难懂的部分在于:
代码如下:
document.documentElement.doScroll("left"); 这实际上是IE浏览器中判断页面加载完毕的一种机制。简单来说,在IE中,当标签加载完毕后,就可以操作Scroll属性。利用这一原理,可以判断IE中页面是否加载完毕。
代码中包含一个_ready函数,该函数用于在页面加载完毕后执行所有已注册的回调函数。代码如下:
(注:页面加载完毕的Ready函数并非原生JS中的window.load,它仅指DOM结构加载完毕。具体细节可自行查阅资料。)
代码如下:
var readyList = [];
var _ready = function(){
while(readyList.length > 0){
var func = readyList.shift();
func();
}
document.removeEventListener("DOMContentLoaded",_ready,false);
} 异步执行队列优化
接下来是本篇的核心内容。先查看相关代码。
代码如下:
Using.asyn = function(callback){
asynQueue.push(callback);
if(!_execAsyn.isRunning){
_execAsyn();
}
} 这里通知Using加载所需模块,但内部引入了asynQueue数组和_execAsyn函数。它们的作用分别是:
asynQueue用于保存异步加载完成后需要回调的函数,本质上是一个数组,可视为创建了一个函数队列。
_execAsyn用于执行保存的回调函数,即逐一执行这些函数。代码如下,每行均有详细注释:
代码如下:
var _execAsyn = function(){
// 创建一个变量来缓存需要执行的函数
var func = null;
// 如果队列中还有未执行的函数 则进行执行操作
if(asynQueue.length > 0){
// 将_execAsyn函数修改为运行状态
_execAsyn.isRunning = true;
// 得到队列中第一个需要执行的函数
func = asynQueue.shift();
// 调用异步加载模块Using.fn.script函数 并传入加载完毕之后需要执行的回调函数
Using.fn.script(function(){
// 当前需要执行的函数
func();
// 迭代_execAsyn 直到队列中没有需要执行的函数
_execAsyn();
});
// 若队列中没有需要执行的函数
}else{
// 则将_execAsyn运行状态改为false
_execAsyn.isRunning = false;
}
} 该函数解释起来并不复杂,即逐一执行队列中的函数。唯一需要注意的是,为何操作队列时采用迭代而非循环?原因如下:
1、队列中随时可能有新函数加入,若采用循环,由于新函数总是插入队列尾部,可能导致最新加入的函数无法被执行。
2、Using.fn.script是异步的。若采用循环,当前函数尚未执行完毕,下一个函数可能已进入执行状态。虽然并行执行多个函数可能提高速率,但此处限制并行执行的原因在于:每次执行队列中的函数可能需要加载相应模块。若多个依赖相同模块的函数并行执行,可能导致同一模块被加载多次,进而引发后续函数执行异常。
至此,UsingJS的核心部分已介绍完毕。其中加入了Using.Class.create函数,该函数在JavaScript动态加载文章的末尾有所提及。
页面使用示例与注意事项
最后,查看页面使用情况示例:
代码如下:
<script type="text/javascript" src="js/using-0.4.2.min.js"></script>
<script type="text/javascript">
Using("jq");
Using("UserView");
Using("jq");
Using.ready(function(){
Using.asyn(function(){
"#panel").click(function(){
alert("by jquery");
});
});
});
Using.ready(function(){
Using("Http");
Using.asyn(function(){
var http = new Using.Modules.Http();
http.set("xxx");
http.show();
});
Using.asyn(function(){
var h = new Using.Modules.Http();
h.set("ooo");
h.show();
});
Using("jq");
Using.asyn(function(){
"#panel").click(function(){
alert("loaded jquery");
});
});
});
</script> 这段代码刻意进行了重复加载,多次触发Ready事件并在Ready之后使用Using导包。
有一个特别需要注意的地方:
代码如下:
Using("Http");
Using.asyn(function(){
var http = new Using.Modules.Http();
http.set("xxx");
http.show();
});
// 假如在这个地方使用
// var ht = new Using.Modules.Http();
// 是会报Using.Modules.Http不是一个constructor
// 原因就是
// 任何操作都是异步的,当执行此句时Using("Http")这个模块载入可能还没有完成
// 这一点是仲谋给多个朋友进行使用时会犯的错误 总以为导包之后 万事大吉
// 是的 本身应该是这样 导包之后 在任何地方都可以随意引用
// 但是总得有个前提吧 那就是模块得加载完毕
// 所以 还请将所有的代码都写在Using.asyn之内
Using.asyn(function(){
var h = new Using.Modules.Http();
h.set("ooo");
h.show();
}); UsingJS下载
JS中将数组和对象混合组装为JSON字符串
2026-11-05 16:41:42
JS运算符优先级与结合性的区别是什么?
2026-11-05 13:22:02
JS Map怎么只遍历Key或Value
2026-11-05 13:00:36
D3.js 与 ECharts 选型对比:灵活性 vs 开发效率
2026-11-05 09:45:01
为什么JS中typeof []返回object:数组类型检测误区解析
2026-11-04 19:50:47
