多浏览器兼容的动态加载JavaScript与CSS
2023-06-13 09:14:02 时间
在介绍ensure内部的实现之前,让我们先来看看其功能:
ensure({
html:"popup.html",
javascript:"popup.js",
css:"popup.css"
},function(){
Popup.show("helloworld");
}
);
在这段代码中,ensure首先会确保popup.html、popup.js、popup.css这3个文件的加载,如果都没加载过ensure就会动态加载它们;如果已经加载过了,ensure不会再次加载。在确保这3个文件都加载后,ensure会调用后面的匿名函数,也就是执行Popup.show("helloworld");。
接下来,就让我们看看ensure是如何动态加载JavaScript与CSS的。
加载JavaScript
在ensure当中,加载JavaScript分两种情况来执行,也就是Safari与非Safari这两种情况。
在IE、Firefox、Opera中加载JavaScript
在这三款浏览器中加载JavaScript,其实只需要创建一个script元素,把src指向要加载的URL,最后把script元素追加到head元素上,那就搞掂了。此项工作是在HttpLibrary.createScriptTag()中完成的。不过我们不仅仅要加载JavaScript,同时还需要知道它什么时候完成加载,这可以通过script元素的onload事件或onreadystatechange事件来实现。
在Safari中加载JavaScript
因为Safari2不支持onload或者onreadystatechange,所以只能手动通过XHR把URL读去过来,然后再手动eval这段代码,这就带来了一个限制──只能加载本域的JavaScript文件。在ensure当中,eval的工作是通过HttpLibrary.globalEval()来完成的。为了让JavaScript代码在全局(global)上下文中eval,ensure还是使用了创建script元素的方法,并将要eval的JavaScript置于其内,最后把script元素追加到head元素内。
细心的人肯定要问,为什么HttpLibrary.globalEval()要如此设计,而非直接window.eval或者eval.call。这是因为,window.eval和eval.call都无法在IE6中实现和script标签加载JavaScript代码一模一样的效果,这两种做法的eval在IE6下仍然不是在全局上下文中执行的。搜索一下你就会发现一些相关的讨论,例如jQuery就曾经使用window.execScript()来完成此项任务。不过最终大家都发现添加script元素才是最好的跨浏览器解决方案,所以现在的jQuery和ensure都是如此实现的了。
加载CSS
相对于加载JavaScript而言,加载CSS就简单多了,而且方法也是类似的:在head元素内直接加入link元素就可以了。这也正是loadCSS()所完成的工作。
实际上,ensure没有确保CSS完成加载后再执行下去。这估计是因为浏览器都能够在CSS加载完成后自动应用到页面上,因此OmarALZabir就认为CSS的加载顺序是无关紧要的,不过假如CSS加载速度实在太慢,其实还是会影响显示效果的。
在IE6中加载CSS
这次需要特别照顾的是IE6,而非Safari。IE6在往head元素添加link元素时,必须在window的上下文中完成,因此添加link的函数通过call调用切换了上下文。
总结
实际上动态加载JavaScript与CSS都并不难,在大多数情况下只需要向head元素追加对应的子元素就可以了,只有Safari2和IE6这两款古老的浏览器是需要特殊照顾的。
官方地址
ensure
复制代码代码如下:
ensure({
html:"popup.html",
javascript:"popup.js",
css:"popup.css"
},function(){
Popup.show("helloworld");
}
);
在这段代码中,ensure首先会确保popup.html、popup.js、popup.css这3个文件的加载,如果都没加载过ensure就会动态加载它们;如果已经加载过了,ensure不会再次加载。在确保这3个文件都加载后,ensure会调用后面的匿名函数,也就是执行Popup.show("helloworld");。
接下来,就让我们看看ensure是如何动态加载JavaScript与CSS的。
加载JavaScript
在ensure当中,加载JavaScript分两种情况来执行,也就是Safari与非Safari这两种情况。
在IE、Firefox、Opera中加载JavaScript
在这三款浏览器中加载JavaScript,其实只需要创建一个script元素,把src指向要加载的URL,最后把script元素追加到head元素上,那就搞掂了。此项工作是在HttpLibrary.createScriptTag()中完成的。不过我们不仅仅要加载JavaScript,同时还需要知道它什么时候完成加载,这可以通过script元素的onload事件或onreadystatechange事件来实现。
在Safari中加载JavaScript
因为Safari2不支持onload或者onreadystatechange,所以只能手动通过XHR把URL读去过来,然后再手动eval这段代码,这就带来了一个限制──只能加载本域的JavaScript文件。在ensure当中,eval的工作是通过HttpLibrary.globalEval()来完成的。为了让JavaScript代码在全局(global)上下文中eval,ensure还是使用了创建script元素的方法,并将要eval的JavaScript置于其内,最后把script元素追加到head元素内。
细心的人肯定要问,为什么HttpLibrary.globalEval()要如此设计,而非直接window.eval或者eval.call。这是因为,window.eval和eval.call都无法在IE6中实现和script标签加载JavaScript代码一模一样的效果,这两种做法的eval在IE6下仍然不是在全局上下文中执行的。搜索一下你就会发现一些相关的讨论,例如jQuery就曾经使用window.execScript()来完成此项任务。不过最终大家都发现添加script元素才是最好的跨浏览器解决方案,所以现在的jQuery和ensure都是如此实现的了。
加载CSS
相对于加载JavaScript而言,加载CSS就简单多了,而且方法也是类似的:在head元素内直接加入link元素就可以了。这也正是loadCSS()所完成的工作。
实际上,ensure没有确保CSS完成加载后再执行下去。这估计是因为浏览器都能够在CSS加载完成后自动应用到页面上,因此OmarALZabir就认为CSS的加载顺序是无关紧要的,不过假如CSS加载速度实在太慢,其实还是会影响显示效果的。
在IE6中加载CSS
这次需要特别照顾的是IE6,而非Safari。IE6在往head元素添加link元素时,必须在window的上下文中完成,因此添加link的函数通过call调用切换了上下文。
总结
实际上动态加载JavaScript与CSS都并不难,在大多数情况下只需要向head元素追加对应的子元素就可以了,只有Safari2和IE6这两款古老的浏览器是需要特殊照顾的。
官方地址
/*
Script:Ensure.js
Ensurelibrary
Atinyjavascriptlibrarythatprovidesahandyfunction"ensure"whichallowsyoutoload
Javascript,HTML,CSSon-demandandthenexecuteyourcode.Ensureensuresthatrelevent
JavascriptandHTMLsnippetsarealreadyinthebrowserDOMbeforeexecutingyourcode
thatusesthem.
Todownloadlastversionofthisscriptusethislink:<http://www.codeplex.com/ensure>
Version:
1.0-Initialrelease
Compatibility:
FireFox-Version2and3
InternetExplorer-Version6and7
Opera-9(probably8too)
Safari-Version2and3
Konqueror-Version3orgreater
Dependencies:
<jQuery.js>
<MicrosoftAJAX.js>
<Prototype-1.6.0.js>
Credits:
-GlobalJavascriptexecution-<http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html>
Author:
OmarALZabir-http://msmvps.com/blogs/omar
License:
>Copyright(C)2008OmarALZabir-http://msmvps.com/blogs/omar
>
>Permissionisherebygranted,freeofcharge,
>toanypersonobtainingacopyofthissoftwareandassociated
>documentationfiles(the"Software"),
>todealintheSoftwarewithoutrestriction,
>includingwithoutlimitationtherightstouse,copy,modify,merge,
>publish,distribute,sublicense,and/orsellcopiesoftheSoftware,
>andtopermitpersonstowhomtheSoftwareisfurnishedtodoso,
>subjecttothefollowingconditions:
>
>Theabovecopyrightnoticeandthispermissionnoticeshallbeincluded
>inallcopiesorsubstantialportionsoftheSoftware.
>
>THESOFTWAREISPROVIDED"ASIS",WITHOUTWARRANTYOFANYKIND,EXPRESSORIMPLIED,
>INCLUDINGBUTNOTLIMITEDTOTHEWARRANTIESOFMERCHANTABILITY,
>FITNESSFORAPARTICULARPURPOSEANDNONINFRINGEMENT.
>INNOEVENTSHALLTHEAUTHORSORCOPYRIGHTHOLDERSBELIABLEFORANYCLAIM,
>DAMAGESOROTHERLIABILITY,WHETHERINANACTIONOFCONTRACT,TORTOROTHERWISE,
>ARISINGFROM,OUTOFORINCONNECTIONWITHTHESOFTWARE
>ORTHEUSEOROTHERDEALINGSINTHESOFTWARE.
*/
(function(){
window.ensure=function(data,callback,scope)
{
if(typeofjQuery=="undefined"&&typeofSys=="undefined"&&typeofPrototype=="undefined")
returnalert("jQuery,MicrosoftASP.NETAJAXorPrototypelibrarynotfound.Onemustbepresentforensuretowork");
//There"satestcriteriawhichwhenfalse,theassociatedcomponentsmustbeloaded.Butiftrue,
//noneedtoloadthecomponents
if(typeofdata.test!="undefined")
{
vartest=function(){returndata.test};
if(typeofdata.test=="string")
{
test=function()
{
//Ifthere"snosuchJavascriptvariableandthere"snosuchDOMelementwithIDthen
//thetestfails.Ifanyexists,thentestsucceeds
return!(eval("typeof"+data.test)=="undefined"
&&document.getElementById(data.test)==null);
}
}
elseif(typeofdata.test=="function")
{
test=data.test;
}
//Nowwehavetestprepared,timetoexecutethetestandseeifitreturnsnull,undefinedorfalseinany
//scenario.Ifitdoes,thenloadthespecifiedjavascript/html/css
if(test()===false||typeoftest()=="undefined"||test()==null)
newensureExecutor(data,callback,scope);
//Testsucceeded!Justfirethecallback
else
callback();
}
else
{
//Notestspecified.So,loadnecessaryjavascript/html/cssandexecutethecallback
newensureExecutor(data,callback,scope);
}
}
//ensureExecutoristhemainclassthatdoesthejobofensure.
window.ensureExecutor=function(data,callback,scope)
{
this.data=this.clone(data);
this.callback=(typeofscope=="undefined"||null==scope?callback:this.delegate(callback,scope));
this.loadStack=[];
if(data.js&&data.js.constructor!=Array)this.data.js=[data.js];
if(data.html&&data.html.constructor!=Array)this.data.html=[data.html];
if(data.css&&data.css.constructor!=Array)this.data.css=[data.css];
if(typeofdata.js=="undefined")this.data.js=[];
if(typeofdata.html=="undefined")this.data.html=[];
if(typeofdata.css=="undefined")this.data.css=[];
this.init();
this.load();
}
window.ensureExecutor.prototype={
init:function()
{
//FetchJavascriptusingFrameworkspecificlibrary
if(typeofjQuery!="undefined")
{
this.getJS=HttpLibrary.loadJavascript_jQuery;
this.httpGet=HttpLibrary.httpGet_jQuery;
}
elseif(typeofPrototype!="undefined")
{
this.getJS=HttpLibrary.loadJavascript_Prototype;
this.httpGet=HttpLibrary.httpGet_Prototype;
}
elseif(typeofSys!="undefined")
{
this.getJS=HttpLibrary.loadJavascript_MSAJAX;
this.httpGet=HttpLibrary.httpGet_MSAJAX;
}
else
{
throw"jQuery,PrototypeorMSAJAXframeworknotfound";
}
},
getJS:function(data)
{
//abstractfunctiontogetJavascriptandexecuteit
},
httpGet:function(url,callback)
{
//abstractfunctiontomakeHTTPGETcall
},
load:function()
{
this.loadJavascripts(this.delegate(function(){
this.loadCSS(this.delegate(function(){
this.loadHtml(this.delegate(function(){
this.callback()
}))
}))
}));
},
loadJavascripts:function(complete)
{
varscriptsToLoad=this.data.js.length;
if(0===scriptsToLoad)returncomplete();
this.forEach(this.data.js,function(href)
{
if(HttpLibrary.isUrlLoaded(href)||this.isTagLoaded("script","src",href))
{
scriptsToLoad--;
}
else
{
this.getJS({
url:href,
success:this.delegate(function(content)
{
scriptsToLoad--;
HttpLibrary.registerUrl(href);
}),
error:this.delegate(function(msg)
{
scriptsToLoad--;
if(typeofthis.data.error=="function")this.data.error(href,msg);
})
});
}
});
//waituntilalltheexternalscriptsaredownloaded
this.until({
test:function(){returnscriptsToLoad===0;},
delay:50,
callback:this.delegate(function()
{
complete();
})
});
},
loadCSS:function(complete)
{
if(0===this.data.css.length)returncomplete();
varhead=HttpLibrary.getHead();
this.forEach(this.data.css,function(href)
{
if(HttpLibrary.isUrlLoaded(href)||this.isTagLoaded("link","href",href))
{
//Donothing
}
else
{
varself=this;
try
{
(function(href,head)
{
varlink=document.createElement("link");
link.setAttribute("href",href);
link.setAttribute("rel","Stylesheet");
link.setAttribute("type","text/css");
head.appendChild(link);
HttpLibrary.registerUrl(href);
}).apply(window,[href,head]);
}
catch(e)
{
if(typeofself.data.error=="function")self.data.error(href,e.message);
}
}
});
complete();
},
loadHtml:function(complete)
{
varhtmlToDownload=this.data.html.length;
if(0===htmlToDownload)returncomplete();
this.forEach(this.data.html,function(href)
{
if(HttpLibrary.isUrlLoaded(href))
{
htmlToDownload--;
}
else
{
this.httpGet({
url:href,
success:this.delegate(function(content)
{
htmlToDownload--;
HttpLibrary.registerUrl(href);
varparent=(this.data.parent||document.body.appendChild(document.createElement("div")));
if(typeofparent=="string")parent=document.getElementById(parent);
parent.innerHTML=content;
}),
error:this.delegate(function(msg)
{
htmlToDownload--;
if(typeofthis.data.error=="function")this.data.error(href,msg);
})
});
}
});
//waituntilalltheexternalscriptsaredownloaded
this.until({
test:function(){returnhtmlToDownload===0;},
delay:50,
callback:this.delegate(function()
{
complete();
})
});
},
clone:function(obj)
{
varcloned={};
for(varpinobj)
{
varx=obj[p];
if(typeofx=="object")
{
if(x.constructor==Array)
{
vara=[];
for(vari=0;i<x.length;i++)a.push(x[i]);
cloned[p]=a;
}
else
{
cloned[p]=this.clone(x);
}
}
else
cloned[p]=x;
}
returncloned;
},
forEach:function(arr,callback)
{
varself=this;
for(vari=0;i<arr.length;i++)
callback.apply(self,[arr[i]]);
},
delegate:function(func,obj)
{
varcontext=obj||this;
returnfunction(){func.apply(context,arguments);}
},
until:function(o/*o={test:function(){...},delay:100,callback:function(){...}}*/)
{
if(o.test()===true)o.callback();
elsewindow.setTimeout(this.delegate(function(){this.until(o);}),o.delay||50);
},
isTagLoaded:function(tagName,attName,value)
{
//Createatemporarytagtoseewhatvaluebrowsereventually
//givestotheattributeafterdoingnecessaryencoding
vartag=document.createElement(tagName);
tag[attName]=value;
vartagFound=false;
vartags=document.getElementsByTagName(tagName);
this.forEach(tags,function(t)
{
if(tag[attName]===t[attName]){tagFound=true;returnfalse}
});
returntagFound;
}
}
varuserAgent=navigator.userAgent.toLowerCase();
//HttpLibraryisacrossbrowser,crossframeworklibrarytoperformcommonoperations
//likeHTTPGET,injectingscriptintoDOM,keepingtrackofloadedurletc.Itprovides
//implementationsforvariousframeworksincludingjQuery,MSAJAXorPrototype
varHttpLibrary={
browser:{
version:(userAgent.match(/.+(?:rv|it|ra|ie)[\/:]([\d.]+)/)||[])[1],
safari:/webkit/.test(userAgent),
opera:/opera/.test(userAgent),
msie:/msie/.test(userAgent)&&!/opera/.test(userAgent),
mozilla:/mozilla/.test(userAgent)&&!/(compatible|webkit)/.test(userAgent)
},
loadedUrls:{},
isUrlLoaded:function(url)
{
returnHttpLibrary.loadedUrls[url]===true;
},
unregisterUrl:function(url)
{
HttpLibrary.loadedUrls[url]=false;
},
registerUrl:function(url)
{
HttpLibrary.loadedUrls[url]=true;
},
createScriptTag:function(url,success,error)
{
varscriptTag=document.createElement("script");
scriptTag.setAttribute("type","text/javascript");
scriptTag.setAttribute("src",url);
scriptTag.onload=scriptTag.onreadystatechange=function()
{
if((!this.readyState||
this.readyState=="loaded"||this.readyState=="complete")){
success();
}
};
scriptTag.onerror=function()
{
error(data.url+"failedtoload");
};
varhead=HttpLibrary.getHead();
head.appendChild(scriptTag);
},
getHead:function()
{
returndocument.getElementsByTagName("head")[0]||document.documentElement
},
globalEval:function(data)
{
varscript=document.createElement("script");
script.type="text/javascript";
if(HttpLibrary.browser.msie)
script.text=data;
else
script.appendChild(document.createTextNode(data));
varhead=HttpLibrary.getHead();
head.appendChild(script);
//head.removeChild(script);
},
loadJavascript_jQuery:function(data)
{
if(HttpLibrary.browser.safari)
{
returnjQuery.ajax({
type:"GET",
url:data.url,
data:null,
success:function(content)
{
HttpLibrary.globalEval(content);
data.success();
},
error:function(xml,status,e)
{
if(xml&&xml.responseText)
data.error(xml.responseText);
else
data.error(url+"\n"+e.message);
},
dataType:"html"
});
}
else
{
HttpLibrary.createScriptTag(data.url,data.success,data.error);
}
},
loadJavascript_MSAJAX:function(data)
{
if(HttpLibrary.browser.safari)
{
varparams=
{
url:data.url,
success:function(content)
{
HttpLibrary.globalEval(content);
data.success(content);
},
error:data.error
};
HttpLibrary.httpGet_MSAJAX(params);
}
else
{
HttpLibrary.createScriptTag(data.url,data.success,data.error);
}
},
loadJavascript_Prototype:function(data)
{
if(HttpLibrary.browser.safari)
{
varparams=
{
url:data.url,
success:function(content)
{
HttpLibrary.globalEval(content);
data.success(content);
},
error:data.error
};
HttpLibrary.httpGet_Prototype(params);
}
else
{
HttpLibrary.createScriptTag(data.url,data.success,data.error);
}
},
httpGet_jQuery:function(data)
{
returnjQuery.ajax({
type:"GET",
url:data.url,
data:null,
success:data.success,
error:function(xml,status,e)
{
if(xml&&xml.responseText)
data.error(xml.responseText);
else
data.error("Erroroccuredwhileloading:"+url+"\n"+e.message);
},
dataType:data.type||"html"
});
},
httpGet_MSAJAX:function(data)
{
var_wRequest=newSys.Net.WebRequest();
_wRequest.set_url(data.url);
_wRequest.set_httpVerb("GET");
_wRequest.add_completed(function(result)
{
varerrorMsg="Failedtoload:"+data.url;
if(result.get_timedOut()){
errorMsg="Timedout";
}
if(result.get_aborted()){
errorMsg="Aborted";
}
if(result.get_responseAvailable())data.success(result.get_responseData());
elsedata.error(errorMsg);
});
varexecutor=newSys.Net.XMLHttpExecutor();
_wRequest.set_executor(executor);
executor.executeRequest();
},
httpGet_Prototype:function(data)
{
newAjax.Request(data.url,{
method:"get",
evalJS:false,//Makesureprototypedoesnotautomaticallyevanscripts
onSuccess:function(transport,json)
{
data.success(transport.responseText||"");
},
onFailure:data.error
});
}
};
})();
相关文章
- 【说站】javascript回文是什么
- 【说站】JavaScript闭包的注意事项
- [javascript] js如何获取浏览器的语言
- 发布、传输和安装现代 JavaScript 以实现更快的应用程序
- JavaScript 常用自定义功能函数
- 在Javascript中定义对象类别
- Javascript实现的分页函数
- JavaScript入门学习书籍推荐
- javascript字符串连接的性能问题(多浏览器)
- Javascript布尔型分析
- JavaScript入门教程(9)Document文档对象
- JavaScript验证浏览器是否支持javascript的方法小结
- !DOCTYPE声明对JavaScript的影响分析
- javascript常用方法、属性集合及NodeList和HTMLCollection的浏览器差异
- Javascript世界时区时间显示代码
- javascript学习笔记(十七)检测浏览器插件代码
- 浏览器的JavaScript引擎的识别方法
- javascript通过navigator.userAgent识别各种浏览器
- javascript禁制后退键(Backspace)实例代码
- 浅析JavaScript中的隐式类型转换
- 你可能不知道的JavaScript的newFunction()方法
- javascript中兼容主流浏览器的动态生成iframe方法
- javascript浏览器兼容教程之事件处理
- 借助JavaScript脚本判断浏览器FlashPlayer信息的方法
- 自己封装的常用javascript函数分享
- JavaScript监听和禁用浏览器回车事件实例
- css与javascript跨浏览器兼容性总结