SCRIPT标签的阻塞行为会对页面的性能产生影响,这是因为浏览器在下载脚本、解析、执行的过程中不会同时做其他事情,比如渲染页面、响应用户事件等。之所以这样做是因为正在执行的JavaScript代码可能会改变页面元素、修改样式、添加或者删除事件等各种操作,以及最关键的脚本之间的依赖性,浏览器必须等待当前执行的脚本执行完成之后再进行后续操作。
HTML页面中的JavaScript脚本有两种方式加入
- 使用script标签内联到HTML页面,页面按从上到下的顺序执行到script标签处时执行js代码,后续HTML内容会被阻塞。
- 使用script标签的src属性将js文件从外部加载,这需要浏览器查询对应文件的缓存,如果不可用就要重新进行http请求,这样就又会产生网络延迟和下载后的解析与执行,这都会阻塞页面。
一般情况下,大多数浏览器支持并行下载html、css、图片等元素,不过对于同一个域名下的资源,浏览器默认最多并行下载的个数有限制,一般是4个。另外,对于JavaScript脚本,浏览器却不支持并行下载,当下载一个脚本并解析、执行后,才能执行后一个。
浏览器之所以保证不能并行下载多个脚本,是为了防止两个有依赖的脚本在浏览器的执行顺序颠倒后,会引发变量、函数未定义的错误。同时也会更改html内容。因此,脚本必须要顺序地执行,但是这并不代表它们必须要顺序地下载。
IE8是第一个支持并行下载JavaScript脚本的浏览器,这使得页面加载多个脚本的速度加快了很多,但是这并没有解决脚本执行的阻塞问题,当A.js和B.js并行下载之后,它们依然要进行解析和顺序执行,这段时间必须要阻塞浏览器的其他行为,也就是等这段时间过了之后才能下载后续图片、css、iframe等元素。Chrome 2+和Safari 4+浏览器也是和IE8类似,会并行下载脚本但是不能
因此,最终的目的是要并行下载脚本的同时,也能下载其他元素而不会有阻塞的问题。这里都以外部脚本文件的加载来说明。
为了避免脚本的阻塞问题,最简单的方式就是讲所有JavaScript脚本内联到HTML中,将脚本放在所有可显示元素最后面,就可以避免这个问题,但是对于大型的js文件以及缓存js文件的考虑,这个问题需要进行折中处理。针对外部的js文件请求,主要有如下几个方式进行
使用XMLHTTPRequest对象从服务器异步获取js脚本文件,当响应完成后使用JavaScript语言的eval函数对响应内容进行执行。
优点:异步请求的js文件不会阻塞其他元素(图片、css等)的下载,脚本异步下载完成之后就执行。浏览器不显示”等待“。
缺点:请求的js文件必须要与主页面在同一个域名下,这对于CDN或者多域名的处理不便。不能保证执行顺序。
var xhrObj = getXHRObject();
xhrObj.onreadystatechange = function(){
if(xhrObj.readyState == 4 && xhrObj.status == 200){
//依赖文件队列处理
eval(xhrObj.responseText);
//后续处理
}
};
xhrObj.open(‘GET‘, ‘a.js‘, true);
xhrObj.send(‘‘);
一般为了保证异步加载的js文件的依赖性,需要手动保存好依赖文件队列,使其按依赖顺序执行。
与XHR Eval类似,XHR Injection将异步获取的内容使用动态创建script标签的形式插入到DOM元素中去。实际测试显示使用eval方法会比这种方法的速度慢一些。
var xhrObj = getXHRObject();
xhrObj.onreadystatechange = function(){
if(xhrObj.readyState == 4 && xhrObj.status == 200){
//依赖文件队列处理
var script = document.createElement(‘script‘);
document.getElementsByTagName(‘head‘)[0].appendChild(script);
script.text = xhrObj.responseText;
//后续处理
}
};
xhrObj.open(‘GET‘, ‘a.js‘, true);
xhrObj.send(‘‘);
这种方法的优缺点与XHR Eval差不多,但是速度可能会快一些。
iframe可以与主页面的其他元素并行下载,并且不会阻塞。但是iframe是为了包含其他的HTML页面,其他的HTML页面也可以包含js脚本,因此可以将需要加载的js脚本放入一个html文件中然后使用iframe非阻塞加载这个html文件即可。
优点:异步加载脚本,浏览器支持较好
缺点:需要与主页面同一个域名,需要将外部的js文件转换为html从而作为iframe的src属性。另外,iframe是一个非常重量级的DOM元素。不能保证执行顺序。浏览器会显示”等待“。
iframe本身也都可以使用js脚本进行动态创建:
var _ = function(d){document.getElementById(d);};
var removeNode = function(a) {
try {
typeof a == "string" && (a = _(a));
a.parentNode.removeChild(a)
} catch (b) {}
};
var addEvent = function(ele, event, call){
ele.addEventListener ? ele.addEventListener(event, call, !1) : a.attachEvent ? a.attachEvent("on" + event, call) : a["on" + event] = call;
};
var loadScriptByIframe = function(id, src){
src == null && (src = "javascript:false;");
removeNode(id);
var c = document.createElement(‘iframe‘);
c.height = 0;
c.width = 0;
c.style.display = "none";
c.name = id;
c.id = id;
c.src = src;
c.isReady = !1;
addEvent(c, "load", function(){
if(! c.isReady){
c.isReady = !0;
//当前脚本已加载完成,执行其他js代码
}
});
document.body.appendChild(c);
window.frames[id].name = id;
return c;
};
在iframe和主页面中的脚本需要进行修改才能互相访问:
- 从主页面中访问iframe
window.frames[0].methodInIframe(); //使用frames
document.getElementById(‘iframeID‘).contentWindow.methodInIframe(); //使用getElementById
function methodInIframe(){
var newDiv = parent.document.createElement(‘div‘);
parent.document.body.appendChild(newDiv);
}
这个方法是使用最多的方法,通过js脚本动态创建script标签插入到DOM中,可以动态设置src属性,并且可以是不同域名下的js文件,一般百度统计、cnzz、google analysis等网站统计工具提供的统计代码就是使用这种方式,是最为人知使用最为广泛的方法。
优点:可以跨域,创建script元素不会阻塞其他元素的下载,并且代码实现简单方便。
缺点:下载后的脚本并不能保证按顺序执行,仅仅在FireFox浏览器下保证顺序执行,其他浏览器存在脚本文件的依赖冲突。浏览器显示”等待“。
var loadScriptByScriptDom(id, src, c){
removeNode(id);
var d = document.getElementsByTagName(‘head‘)[0],
e = document.createElement(‘script‘);
e.charset = c || ‘utf-8‘;
e.id = id;
e.type = ‘text/javascript‘;
e.src = src;
d.appendChild(e);
}
IE浏览器的script标签支持defer属性,当使用这个属性之后,浏览器会识别,表示不会立即下载这个script标签对于的js文件,当这个js文件中没有document.write调用,并且没有其他脚本依赖这个文件,那么就可以使用。IE在下载这个文件时不会阻塞其他元素的下载。
优点:实现方便,保证js文件执行的顺序,支持跨域
缺点:浏览器会显示”等待”状态,仅仅支持defer属性的IE浏览器适用。
<script defer src="a.js"></script>
JavaScript语言中可以调用document的write方法将内容写入到html的DOM中,这种方式仅仅在IE中并行下载,并且在下载的过程中其他资源依然会被阻塞。并不是一种好的解决方式。
document.write("<script type=‘text/javascript‘ src=‘a.js‘></script>")
浏览器等待的标志包括:状态栏、进度条、tab图标、光标形状,另外还有阻塞的渲染和阻塞的onload事件,前者是在当前下载的js脚本之后的可视内容都被阻塞从而不会渲染,后者是只有所有资源都下载完成呈现页面之后这个事件才会触发。
绝大多数浏览器的”等待”状态都会被使用script的src方式加载的过程中被触发,因此会被用户感知到页面还未完成加载。但是这些状态标识不会被基于XHR的XHR Eval和XHR Injection方法触发。
当使用上述的方法加载多个js脚本时,如果他们之间有依赖关系,那么必须保证其执行顺序不变,否则会出现各种未定义的错误。大多数情况下,这不仅与方法有关,也与浏览器有关,使用script 的src属性的方法都保证执行顺序与页面中排布的顺序一致。
对于IE浏览器,script defer和document.write script tag方法保证其执行顺序与排布顺序一致。
FireFox浏览器,不支持script defer属性,同时document.write方法也不能进行并行下载。但是script DOM方法可以在FireFox浏览器中按照其在页面中的排布顺序来执行,但是其他浏览器却不行。
上述基于XHR的方法是不能保证执行的顺序的,但是可以通过下载队列的手动处理后来保证其执行顺序。
根据上述多方面的评价,document.write方法不仅对浏览器的依赖很严重,自身也依然会阻塞后续资源,因此应该避免使用,其他方式针对不同浏览器情况,以及是否需要保证执行顺序和是否显示浏览器的“等待”标志,需要区别对待才行。最终版本如下:
OS = function(){
var dom = document,
me = this;
this.addEvent = function(ele, event, call){
ele.addEventListener ? ele.addEventListener(event, call, !1) : a.attachEvent ? a.attachEvent("on" + event, call) : a["on" + event] = call;
};
this.getXHRObject = function(){
var xhrObj = false;
try{
xhrObj = new XMLHttpRequest();
}catch(e){
var msTypes = ["Msxml2.XMLHTTP.6.0",
"Msxml2.XMLHTTP.3.0",
"Msxml2.XMLHTTP",
"Microsoft.XMLHTTP"];
var l = msTypes.length;
for(var i = 0; i < l; i++){
try{
xhrObj = new ActiveXObject(msTypes[i]);
}catch(e){
continue;
}
break;
}
}finally{
return xhrObj;
}
};
this.LoadScript = {
Default : function(url, onload){
me.LoadScript .DomElement(url, onload);
},
DomElement : function(url, onload){
var domscript = dom.createElement(‘script‘);
domscript.src = url;
if (onload){
domscript.onloadDone = false;
domscript.onload = onload;
domscript.onreadystatechange = function(){
if ("loaded" === domscript.readyState && domscript.onloadDone){
domscript.onloadDone = true;
domscript.onload();
}
}
}
dom.getElementsByTagName(‘head‘)[0].appendChild(domscript);
},
DocWrite : function(url, onload){
document.write(‘<scr‘ + ‘ipt src="‘ + url +
‘" type=text/javascript"></scr‘ + ‘ipt>‘);
if (onload){
me.addEvent(window, "load", onload);
}
},
queuedScripts : new Array(),
XhrInjection : function(url, onload, isOredered){
var q = me.LoadScript.queuedScripts.length;
if (isOredered){
var qScript = {response: null, onload:onload, done: false};
me.LoadScript.queuedScripts[q] = qScript;
}
var xhrObj = me.getXHRObject();
xhrObj.onreadystatechange = function(){
if (xhrObj.readyState == 4){
if (isOrdered){
me.LoadScript.queuedScripts[q].response =
xhrObj.responseText;
me.LoadScript.injectScripts();
}else{
eval(xhrObj.responseText);
if(onload) onload();
}
}
};
xhrObj.open(‘GET‘, url, true);
xhrObj.send(‘‘);
},
injectScripts : function(){
var queue = me.LoadScript.queuedScripts;
var len = queue.length;
for (var i = 0; i < len; i++){
var qScript = queue[i];
if( ! qScript.done ){
break;
}else{
eval(qScript.response);
if (qScript.onload){
qScript.onload();
}
qScript.done = true;
}
}
},
};
};//end of OS
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/u010487568/article/details/47174203