[转载]详解主流浏览器多个外部JavaScript请求和执行机制

[转载]详解主流浏览器多个外部JS请求和执行机制 – Simon4545的IT生活 – 博客园.

在IE8,Firefox3.6之前页面加载外部的JavaScript文件(IE6,7会连同图片,样式资源和页面渲染一同阻塞)是阻塞式的,而 在之后的版本中,浏览器都使用了瀑布式加载,这样页面的打开及渲染速度都会变快,请注意,我提到的瀑布式加载,仅仅指的是加载,而非JS的执行,在主流浏 览器中JS的执行总是阻塞的。用简单一点的语言描述,就是同一时间,页面只会加载一个js文件。在第一个js文件加载并执行完之前,第二个要引入的js不 会下载和执行。而页面中js的引入顺序以请求的顺序为定。
我们来看一个IE6下,页面加载多个js文件的例子:
inline script block:为页面内嵌js代码块,而external script则是需要从外部加载进来的js文件。

image我 们设定inline script执行用时3秒,external script1加载用时2秒,执行用时3秒,external script2加载用时2秒,执行用0秒(实际上会稍大于0,下同),external script3加载用2秒,执行用时0秒。
由上图,可知页面需要15641毫秒才能将js加载并执行完。我们可以得到一个公式:

image

而同样的页面,在IE8和Firefox3.6下,我们会得到:
IE8: (只需要9秒,速度快了近一倍)

image

Firefox:

image

由以上的现象,可以证实上面的观点。

那么是否有一个方案可以让IE6/7或是Chrome展示我们的页面更快一点,可以同时加载多个文件,并且不影响页面中DOM元素的渲染?答案是肯定的。

在之前的项目中,我曾经写过类似的代码,来处理请求多个外部JavaScript文件。

function loadScript(url, fn, doc, charset){
doc
= doc || document;
var script = doc.createElement(script);
script.language
= JavaScript;
script.charset
= charset?charset:’utf8’;
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文件时,我们可以这样写:

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')});
 上段代码处理这样的功能:可以动态加载多个js文件,当文件下载完成后可以执行回调函数。当然它的好外不止于此,我们还可以按需获取,减少流量和浏览器内存占用!对于高并发请求的网站,有着天大的好处!我以前在的一家公司做过统计首页减少50k的流量,一年就可以节省十几万的成本呀。。。但上面的代码也不是完美的,在一些应用下会出现新的问题。请看下面的截图
image

image

不难发现,加载的顺序变了!这时,如果a.js依赖于b.js,而b.js偏偏又迟于a.js加载完成时,就会出现“变量未定义”的js错误,无法执行下去。

那我们的下一个目标就是如何让这些外部文件有序的加载进网页。这样无论智商高的还是低的,都会想到 队列 ,不错,就是队列。在JS里实现队列并不难办,是的,数组就行,我们可以用数组的shift方法,弹出数组的第一个JS文件(这也是我们最先加入数组的那 个JS文件),模拟了队列的实现方法。不过,在这里我们还要多考虑一个问题:必须要上一个js文件加载完成后,下一个JS的请求才能开始。我们怎么判断上 一个JS文件已经加载完成了呢?上代码:

var testNode = doc.createElement(script), fn, node;
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判断。

代码

function 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();
}
}
}

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas,”Courier New”,courier,monospace; background-color: rgb(255, 255, 255); }.csharpcode pre { margin: 0em; }.csharpcode .rem { color: rgb(0, 128, 0); }.csharpcode .kwrd { color: rgb(0, 0, 255); }.csharpcode .str { color: rgb(0, 96, 128); }.csharpcode .op { color: rgb(0, 0, 192); }.csharpcode .preproc { color: rgb(204, 102, 51); }.csharpcode .asp { background-color: rgb(255, 255, 0); }.csharpcode .html { color: rgb(128, 0, 0); }.csharpcode .attr { color: rgb(255, 0, 0); }.csharpcode .alt { background-color: rgb(244, 244, 244); width: 100%; margin: 0em; }.csharpcode .lnum { color: rgb(96, 96, 96); }早期的做法是用window.onload或是defer,defer并不是所有的浏览器都支持,可以排除。 window.onload事件是待到页面上的所有资源被加载才激活,如果页面上有许多图片或音乐,而我们要操作的元素在的它们的下方呢?因此,W3C搞 了DOMContentLoaded与addEventListener,只是可惜IE又不支持。还好,我们又找到readystatechange。知 道页面的内容加载完成,我们再用到Diego Perini提供的doScroll来判断document节点是否渲染到页面。这样我们就能够让js在页面渲染完成后再执行了。至些我们就实现了以下的 功能:

  1. 异步加载,加快了页面的加载时间,同时能够按需加载,节省流量
  2. 有序加载,解决js依赖问题
  3. 延时执行,在页面渲染完成后再执行js,防止undefined情况
附上最后实现的代码:/Files/simon4545/nc.loader.rar
赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏