console.log('your.js: time='+Date.parse(newDate()));functionmyAlert(msg){console.log('alert at '+Date.parse(newDate()));alert(msg);}functionmyLog(msg){console.log(msg);}
Loader=(function(){varloadScript=function(url){varscript=document.createElement('script');script.setAttribute('src',url+'?'+'time='+Date.parse(newDate()));// 不用缓存document.body.appendChild(script);};varloadMultiScript=function(url_array){for(varidx=0;idx<url_array.length;idx++){loadScript(url_array[idx]);}}return{load: loadMultiScript,};})();// end Loader
Loader=(function(){varload_cursor=0;varload_queue;varloadFinished=function(){load_cursor++;if(load_cursor<load_queue.length){loadScript();}}functionloadError(oError){console.error("The script "+oError.target.src+" is not accessible.");}varloadScript=function(){varurl=load_queue[load_cursor];varscript=document.createElement('script');script.type="text/javascript";if(script.readyState){//IEscript.onreadystatechange=function(){if(script.readyState=="loaded"||script.readyState=="complete"){script.onreadystatechange= null;loadFinished();}};}else{//Othersscript.onload=function(){loadFinished();};}script.onerror=loadError;script.src=url+'?'+'time='+Date.parse(newDate());document.body.appendChild(script);};varloadMultiScript=function(url_array){load_cursor=0;load_queue=url_array;loadScript();}return{load: loadMultiScript,};})();// end Loader//loading ...Loader.load(['http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js','./js/your.js','./js/my.js']);
Loader=(function(){vargroup_queue;// group listvargroup_cursor=0;// current group cursorvarcurrent_group_finished=0;varloadFinished=function(){current_group_finished++;if(current_group_finished==group_queue[group_cursor].length){next_group();loadGroup();}};varnext_group=function(){current_group_finished=0;group_cursor++;};varloadError=function(oError){console.error("The script "+oError.target.src+" is not accessible.");};varloadScript=function(url){console.log("load "+url);varscript=document.createElement('script');script.type="text/javascript";if(script.readyState){//IEscript.onreadystatechange=function(){if(script.readyState=="loaded"||script.readyState=="complete"){script.onreadystatechange= null;loadFinished();}};}else{//Othersscript.onload=function(){loadFinished();};}script.onerror=loadError;script.src=url+'?'+'time='+Date.parse(newDate());document.body.appendChild(script);};varloadGroup=function(){if(group_cursor >= group_queue.length)return;current_group_finished=0;for(varidx=0;idx<group_queue[group_cursor].length;idx++){loadScript(group_queue[group_cursor][idx]);}};varloadMultiGroup=function(url_groups){group_cursor=0;group_queue=url_groups;loadGroup();}return{load: loadMultiGroup,};})();// end Loader//loadingvarjquery='http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js',your='./js/your.js',my='./js/my.js';Loader.load([[jquery,your],[my]]);
Loader=(function(){vargroup_queue=[];// group listvarcurrent_group_finished=0;varfinish_callback;varfinish_context;varloadFinished=function(){current_group_finished++;if(current_group_finished==group_queue[0].length){next_group();loadGroup();}};varnext_group=function(){group_queue.shift();};varloadError=function(oError){console.error("The script "+oError.target.src+" is not accessible.");};varloadScript=function(url){console.log("load "+url);varscript=document.createElement('script');script.type="text/javascript";if(script.readyState){//IEscript.onreadystatechange=function(){if(script.readyState=="loaded"||script.readyState=="complete"){script.onreadystatechange= null;loadFinished();}};}else{//Othersscript.onload=function(){loadFinished();};}script.onerror=loadError;script.src=url+'?'+'time='+Date.parse(newDate());document.body.appendChild(script);};varloadGroup=function(){if(group_queue.length==0){finish_callback.call(finish_context);return;}current_group_finished=0;for(varidx=0;idx<group_queue[0].length;idx++){loadScript(group_queue[0][idx]);}};varaddGroup=function(url_array){if(url_array.length>0){group_queue.push(url_array);}};varfire=function(callback,context){finish_callback=callback||function(){};finish_context=context||{};loadGroup();};varinstanceAPI={load : function(){addGroup([].slice.call(arguments));returninstanceAPI;},done : fire,};returninstanceAPI;})();// end Loader//loadingvarjquery='http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js',your='./js/your.js',my='./js/my.js';// Loader.load(jquery, your).load(my).done();Loader.load(jquery,your).load(my).done(function(){console.log(this.msg)},{msg: 'finished'});
// 这里调试用的代码我没有删除Loader=(function(){vargroup_queue=[];// group list//// url_item = {url:str, start: false, finished:false}// 用于调试varlog=function(msg){return;console.log(msg);}varisFunc=function(obj){returnObject.prototype.toString.call(obj)=="[object Function]";}varisArray=function(obj){returnObject.prototype.toString.call(obj)=="[object Array]";}varisAllStart=function(url_items){for(varidx=0;idx<url_items.length;++idx){if(url_items[idx].start==false)returnfalse;}returntrue;}varisAnyStart=function(url_items){for(varidx=0;idx<url_items.length;++idx){if(url_items[idx].start==true)returntrue;}returnfalse;}varisAllFinished=function(url_items){for(varidx=0;idx<url_items.length;++idx){if(url_items[idx].finished==false)returnfalse;}returntrue;}varisAnyFinished=function(url_items){for(varidx=0;idx<url_items.length;++idx){if(url_items[idx].finished==true)returntrue;}returnfalse;}varloadFinished=function(){nextGroup();};varshowGroupInfo=function(){for(varidx=0;idx<group_queue.length;idx++){group=group_queue[idx];if(isArray(group)){log('**********************');for(vari=0;i<group.length;i++){log('url: '+group[i].url);log('start: '+group[i].start);log('finished:'+group[i].finished);log('-------------------');}log('isAllStart: '+isAllStart(group));log('isAnyStart: '+isAnyStart(group));log('isAllFinished: '+isAllFinished(group));log('isAnyFinished: '+isAnyFinished(group));log('**********************');}}};varnextGroup=function(){while(group_queue.length>0){showGroupInfo();// is Funcif(isFunc(group_queue[0])){log('## nextGroup: exec func');group_queue[0]();// execgroup_queue.shift();continue;// is Array}elseif(isAllFinished(group_queue[0])){log('## current group all finished');group_queue.shift();continue;}elseif(!isAnyStart(group_queue[0])){log('## current group no one start!');loadGroup();break;}else{break;}}};varloadError=function(oError){console.error("The script "+oError.target.src+" is not accessible.");};varloadScript=function(url_item){log("load "+url_item.url);url=url_item.url;url_item.start=true;varscript=document.createElement('script');script.type="text/javascript";if(script.readyState){//IEscript.onreadystatechange=function(){if(script.readyState=="loaded"||script.readyState=="complete"){script.onreadystatechange= null;url_item.finished=true;loadFinished();}};}else{//Othersscript.onload=function(){url_item.finished=true;loadFinished();};}script.onerror=loadError;script.src=url+'?'+'time='+Date.parse(newDate());document.body.appendChild(script);};varloadGroup=function(){for(varidx=0;idx<group_queue[0].length;idx++){loadScript(group_queue[0][idx]);}};varaddGroup=function(url_array){log('add :'+url_array);if(url_array.length>0){group=[];for(varidx=0;idx<url_array.length;idx++){url_item={url: url_array[idx],start: false,finished: false,};group.push(url_item);}group_queue.push(group);}nextGroup();};varaddFunc=function(callback){callback&&isFunc(callback)&&group_queue.push(callback);log(group_queue);nextGroup();};varinstanceAPI={load : function(){addGroup([].slice.call(arguments));returninstanceAPI;},wait : function(callback){addFunc(callback);returninstanceAPI;}};returninstanceAPI;})();// end Loader,这尼玛就是一个状态机// loadingvarjquery='http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js',your='./js/your.js',my='./js/my.js';// Loader.load(jquery, your).load(my);Loader.load(jquery,your).wait(function(){console.log("yeah, jquery and your.js were loaded")}).load(my).wait(function(){console.log("yeah, my.js was loaded")});
functiongetJS(url){returnnewPromise(function(resolve,reject){varscript=document.createElement('script');script.type="text/javascript";if(script.readyState){//IEscript.onreadystatechange=function(){if(script.readyState=="loaded"||script.readyState=="complete"){script.onreadystatechange= null;resolve('success: '+url);}};}else{//Othersscript.onload=function(){resolve('success: '+url);};}script.onerror=function(){reject(Error(url+'load error!'));};script.src=url+'?'+'time='+Date.parse(newDate());document.body.appendChild(script);});}functionspawn(generatorFunc){functioncontinuer(verb,arg){varresult;try{result=generator[verb](arg);// 这个result是生成器的返回值,有value和done两个属性}catch(err){returnPromise.reject(err);}if(result.done){returnresult.value;}else{returnPromise.resolve(result.value).then(onFulfilled,onRejected);// result.value是promise对象}}vargenerator=generatorFunc();varonFulfilled=continuer.bind(continuer,"next");varonRejected=continuer.bind(continuer,"throw");returnonFulfilled();}//// loadingvarjquery='http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js',your='./js/your.js',my='./js/my.js';// “串行”代码在这里spawn(function*(){try{yieldgetJS(jquery);console.log('jquery has loaded');yieldgetJS(your);console.log('your.js has loaded');yieldgetJS(my);console.log('my.js has loaded');}catch(err){console.log(err);}});
转载:https://iblog.wilee.me/post/detail/8
最近在做一个为网页生成目录的工具awesome-toc,该工具提供了以jquery插件的形式使用的代码,也提供了一个基于Bookmarklet(小书签)的浏览器插件。
小书签需要向网页中注入多个js文件,也就相当于动态加载js文件。在编写这部分代码时候遇到坑了,于是深究了一段时间。
我在这里整理了动态加载js文件的若干思路,这对于理解异步编程很有用处,而且也适用于Nodejs。
一、硬编码在html源码中的script是如何加载的
如果html中有:
那么,浏览器解析到
会停止渲染页面,去拉取
1.js
(IO操作),等到1.js
的内容获取到后执行。1.js执行完毕后,浏览器解析到
进行和
1.js
类似的操作。不过现在部分浏览器支持async属性和defer属性,这个可以参考:
async vs defer attributes
script的defer和async
script -MDN指出:async对内联脚本(inline script)没有影响,defer的话因浏览器以及版本不同而影响不同。
二、从一个例子出发
举个实际的例子:
js/your.js:
js/my.js:
可以看出
jquery
、js/your.js
、js/my.js
三者的关系如下:js/my.js
依赖于jquery
和js/your.js
。jquery
和js/your.js
之间没有依赖关系。浏览器打开
index00.html
,等待js加载完毕,点击按钮hello world
将会触发alert("hello world");
。firbug控制台输出:
下面开始探索如何动态加载js文件。
三、动态加载js文件的姿势
1、 一个错误的加载方式
文件js/loader01.js内容如下:
index01.html内容如下:
浏览器打开
index01.html
,点击按钮hello world
,会发现什么都没发生。打开firebug,进入控制台,可以看到这样的错误:很明显,
my.js
没等jquery就先执行了。又由于存在依赖关系,脚本的执行出现了错误。这不是我想要的。在网上可以找到关于动态加载的一些说明,例如:
真够乱的!!(这段描述来自:LABJS源码浅析。)
为了解决我们遇到的问题,我们可以在loadScript函数中修改script对象async的值:
浏览器打开,发现可以正常执行!可惜该方法只在某些浏览器的某些版本中有效,没有通用性。script browser compatibility给出了下面的兼容性列表:
下面探索的方法都可以正确的加载和执行多个脚本,不过有些同样有兼容性问题(例如Pormise方式)。
2、正确的加载姿势
可以认为绝大部分浏览器动态加载脚本的方式如下:
所以我们的示例中的三个js脚本的加载和执行顺序可以是下面的情况之一:
jquery
加载并执行,js/your.js
加载并执行,js/my.js
加载并执行。js/your.js
在前,jquery
在后。jquery
和js/your.js
并行加载,按照加载完毕的顺序来执行;等jquery
和js/your.js
都执行完毕后,加载并执行js/my.js
。其中,“加载完毕”这是一个事件,浏览器的支持监测这个事件。这个事件在IE下是
onreadystatechange
,其他浏览器下是onload
。据此,Loading JavaScript without blocking给出了下面的代码:
callback函数可以是去加载另外一个js,不过如果要加载的js文件较多,就成了“回调地狱”(callback hell)。
回调地狱式可以通过一些模式来解决,例如下面给出的方式2:
load_queue
是一个队列,保存需要依次加载的js的url。当一个js加载完毕后,load_cursor++
用来模拟出队操作,然后加载下一个脚本。onerror事件也添加了回调,用来处理无法加载的js文件。当遇到无法加载的js文件时停止加载,剩下的文件也不会加载了。
效果如下:
3、再优雅一点...
方式2是串行的去加载,我们稍加改进,让可以并行加载的js脚本尽可能地并行加载。
Loader.load([ [jquery, your], [my] ]);
代表着jquery
和js/your.js
先尽可能快地加载和执行,等它们执行结束后,加载并执行./js/my.js
。这里将每个子数组里的所有url看成一个group,group内部的脚本尽可能并行加载并执行,group之间则为串行。
这段代码里使用了一个计数器
current_group_finished
记录当前group中完成的url的数量,在这个数量和url的总数一致时,进入下一个group。效果如下:
4、更优雅一点...
该方式是对方式3中代码的重构。
在调用多次load()函数后,必须调用done()函数。done()函数用来触发所有脚本的load。
5、更进一步...
这个方式是对方式4的重写。改进为调用load()时候尽可能去触发实际的load操作。
上面的调用中,每次load时候会尝试马上加载和执行这些脚本,而不是像方式4那样要等done()被调用。
另外出现了新的函数wait,当wait之前的load和wait执行结束后,该wait中的匿名函数会被调用。
效果如下:
6、基于Promise+串行的思路
Promise是一种设计模式。关于Promise,下面的几篇文章值得一看:
当前浏览器对Promise的支持情况如下:
使用Promise解决脚本动态加载问题的方案如下:
这个实现中js是串行加载的。
效果如下:
7、基于Promise+并行
可以使用Promise.all使
jquery
和js/your.js
并行加载。8、基于Generator+Promise
Promise配合生成器(Generator)可以让js程序按照串行的思维编写。
关于生成器,下面的几篇文章值得一看:
浏览器的支持情况如下:
来两个典型的生成器示例:
示例1:
输出:
示例2:
输出:
下面的文章介绍了如何搭配Promise和Generator:
Generator+Promise实现js脚本动态加载的方式如下:
效果如下:
四、其他
1、已有的轮子
在For Your Script Loading Needs列出了许多工具,例如lazyload、LABjs、RequireJS等。
有些工具也提供了新的思路,例如LABjs中可以使用ajax获取同域下的js文件。
2、参考:
1、开源工具
2、资料
浏览器并行加载,顺序解析
模拟浏览器加载和执行js的方案,把加载和执行分开,用XmlRequest并行加载资源,然后用eval按依赖顺序执行代码,跨域和错误都需要自己处理