[转载]详解主流浏览器多个外部JS请求和执行机制 – Simon4545的IT生活 – 博客园.
在IE8,Firefox3.6之前页面加载外部的JavaScript文件(IE6,7会连同图片,样式资源和页面渲染一同阻塞)是阻塞式的,而 在之后的版本中,浏览器都使用了瀑布式加载,这样页面的打开及渲染速度都会变快,请注意,我提到的瀑布式加载,仅仅指的是加载,而非JS的执行,在主流浏 览器中JS的执行总是阻塞的。用简单一点的语言描述,就是同一时间,页面只会加载一个js文件。在第一个js文件加载并执行完之前,第二个要引入的js不 会下载和执行。而页面中js的引入顺序以请求的顺序为定。
我们来看一个IE6下,页面加载多个js文件的例子:
inline script block:为页面内嵌js代码块,而external script则是需要从外部加载进来的js文件。
我 们设定inline script执行用时3秒,external script1加载用时2秒,执行用时3秒,external script2加载用时2秒,执行用0秒(实际上会稍大于0,下同),external script3加载用2秒,执行用时0秒。
由上图,可知页面需要15641毫秒才能将js加载并执行完。我们可以得到一个公式:
而同样的页面,在IE8和Firefox3.6下,我们会得到:
IE8: (只需要9秒,速度快了近一倍)
Firefox:
由以上的现象,可以证实上面的观点。
那么是否有一个方案可以让IE6/7或是Chrome展示我们的页面更快一点,可以同时加载多个文件,并且不影响页面中DOM元素的渲染?答案是肯定的。
在之前的项目中,我曾经写过类似的代码,来处理请求多个外部JavaScript文件。
doc = doc || document;
var script = doc.createElement(‘script‘);
script.language = “JavaScript“;
script.charset = charset?charset:’utf–8’;
script.type = ‘text/javascript‘;
script.onload
= script.onreadystatechange = function(){if (!script.readyState || ‘loaded‘ === script.readyState || ‘complete‘ === script.readyState) {
fn && fn();
script.onload = script.onreadystatechange = null;
script.parentNode.removeChild(script);
};
};
script.src = url;
$(‘head‘)[0].appendChild(script);
}
那个当网页中需要动态调用多个js文件时,我们可以这样写:上段代码处理这样的功能:可以动态加载多个js文件,当文件下载完成后可以执行回调函数。当然它的好外不止于此,我们还可以按需获取,减少流量和浏览器内存占用!对于高并发请求的网站,有着天大的好处!我以前在的一家公司做过统计首页减少50k的流量,一年就可以节省十几万的成本呀。。。但上面的代码也不是完美的,在一些应用下会出现新的问题。请看下面的截图loadScript('../jquery-1.4.2.js',function(){console.log('jquery-1.4.2.js loaded')}); loadScript('$.wbx.js',function(){console.log('example/$.wbx.js loaded')}); loadScript('gadgets.js',function(){console.log('example/gadgets.js loaded')}); loadScript('jui-all.js',function(){console.log('example/jui-all.js loaded')});
不难发现,加载的顺序变了!这时,如果a.js依赖于b.js,而b.js偏偏又迟于a.js加载完成时,就会出现“变量未定义”的js错误,无法执行下去。
那我们的下一个目标就是如何让这些外部文件有序的加载进网页。这样无论智商高的还是低的,都会想到 队列 ,不错,就是队列。在JS里实现队列并不难办,是的,数组就行,我们可以用数组的shift方法,弹出数组的第一个JS文件(这也是我们最先加入数组的那 个JS文件),模拟了队列的实现方法。不过,在这里我们还要多考虑一个问题:必须要上一个js文件加载完成后,下一个JS的请求才能开始。我们怎么判断上 一个JS文件已经加载完成了呢?上代码:
fn = testNode.readyState ? function(node, callback){
node.onreadystatechange = function(){
var rs = node.readyState;
if (rs === ‘loaded‘ || rs === ‘complete‘) {
// handle memory leak in IE
node.onreadystatechange = null;
callback.call(this);
}
};
}: function(node, callback){
node.onload = callback;
};
在非IE情况下我们可以很方便的用dom元素的onload和onerror来判断元素是否已经加载完成,而IE不吃这套,它并不支持script 节点的onload判断,所以我们只好寻求其它的解决方案。还好,IE对onreadystatechange和readyState的支持足以让我们完 成这个任务。
readyState 的值 可能为 以下几个 :
“uninitialized” – 原始状态
“loading” – 下载数据中..
“loaded” – 下载完成
“interactive” – 还未执行完毕.
“complete” – 脚本执行完毕.
在IE6/7/8下,虽然script节点加载完成,但结果并不总是loaded或是complete,并且先设置src再append到节点树和 先append到节点树再设定src,IE7/8处理加载src文件的时间是不同的。为了减少不必要的麻烦,我们这里就两个状态都判断了。
接下来还有一个问题,如果其中某个js文件,需要去处理dom节点,而我们在加载js时并不能确定这个节点是否已经渲染完成,既我们的Js加载需要在所有的dom节点渲染完成后才开始加载执行,这时我们就要用到经典的domReady判断。
if (readyBound) {
return;
}
readyBound
= true; if (document.readyState === “complete“) {return dequeue();
} if (document.addEventListener) {
document.addEventListener(“DOMContentLoaded“, dequeue, false);
window.addEventListener(“load“, dequeue, false);
}
else
if (document.attachEvent) {
document.attachEvent(“onreadystatechange“, dequeue);
window.attachEvent(
“onload“, dequeue);var toplevel = false;
try {
toplevel = window.frameElement == null;
}
catch (e) {
} if (document.documentElement.doScroll && toplevel) {
try {
// If IE is used, use the trick by Diego Perini
// http://javascript.nwbox.com/IEContentLoaded/
document.documentElement.doScroll(“left“);
}
catch (error) {
setTimeout(arguments.callee, 10);
return;
}
dequeue();
}
}
}
- 异步加载,加快了页面的加载时间,同时能够按需加载,节省流量
- 有序加载,解决js依赖问题
- 延时执行,在页面渲染完成后再执行js,防止undefined情况