You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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);}});
4687967079fe42e1fd7c8cc257c2abeb.js:formatted:
1 Failed to execute 'write' on 'Document':
It isn't possible to write into a document from an asynchronously-loaded external script unless it is explicitly opened
最近在做一个为网页生成目录的工具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、资料
The text was updated successfully, but these errors were encountered: