码迷,mamicode.com
首页 > Web开发 > 详细

02 详解主流浏览器多个外部JS请求和执行机制

时间:2015-06-26 06:51:53      阅读:215      评论:0      收藏:0      [点我收藏+]

标签:

在IE8、Firefox3.6之前页面加载外部的javascript文件(IE6和IE7会连同图片,样式资源和页面渲染一同阻塞)是阻塞式的,而在之后的版本中,浏览器都使用了瀑布式加载,这样页面的打开及渲染速度都会变快,请注意,我提到的瀑布式加载,仅仅指的是加载,而非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加载并执行完。我们可以得到一个公式:

技术分享

而同样的页面,在IE8Firefox3.6下,我们会得到:

IE8: (只需要9秒,速度快了近一倍)

技术分享

Firefox:

技术分享

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

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

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

function loadScript(url, fn, doc, charset){

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文件时,我们可以这样写:

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的流量,一年就可以节省十几万的成本呀。。。但上面的代码也不是完美的,在一些应用下会出现新的问题。请看下面的截图

技术分享

技术分享

不难发现,加载的顺序变了!这时,如果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/IE7/IE8下,虽然script节点加载完成,但结果并不总是loaded或是complete,并且先设置src再append到节点树和先append到节点树再设定src,IE7/IE8处理加载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();

}

}

}

早期的做法是用window.onload或是defer,defer并不是所有的浏览器都支持,可以排除。window.onload事件是待到页面上的所有资源被加载才激活,如果页面上有许多图片或音乐,而我们要操作的元素在的它们的下方呢?因此,W3C搞了DOMContentLoaded与addEventListener,只是可惜IE又不支持。还好,我们又找到readystatechange。知道页面的内容加载完成,我们再用到Diego Perini提供的doScroll来判断document节点是否渲染到页面。这样我们就能够让js在页面渲染完成后再执行了。至些我们就实现了以下的功能:

  1. 异步加载,加快了页面的加载时间,同时能够按需加载,节省流量
  2. 有序加载,解决js依赖问题
  3. 延时执行,在页面渲染完成后再执行js,防止undefined情况

附上最后实现的代码:nc.loader.rar

via:福克

02 详解主流浏览器多个外部JS请求和执行机制

标签:

原文地址:http://www.cnblogs.com/agronblogs/p/4601389.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!