Firefox: EnableChrome: EnableOpera: EnableSafari: EnableIE: Enable

无阻塞加载外部脚本

2010
Dec
29
Tags: , ,
4条评论

什么是脚本阻塞?

浏览器在加载外部脚本时,不会进行其他内容的加载和渲染工作,相当于此时浏览器处于一种“停滞”状态,直至该外部脚本完全加载,并执行完成。这样的设计具有其合理性和必须性,每一个脚本都有可能对页面元素进行操作,同时相互之间可能存在依赖性,最简单的例子如使用 jQuery 这样的库,基于 jQuery 的脚本必须在 jQuery 完全下载并执行完成建立 jQuery 对象后,其他的代码才有生效的可能。由此看来,脚本按顺序执行时必需的,但是没有更好的理由表示他们需要按顺序单个加载。这样的做法严重影响了页面的性能,当页面含有较多的外部脚本,可以非常明显的感觉到浏览器被脚本阻塞而处于这种”停滞“状态。下面的一些方法将帮助我们并行下载外部脚本。

1. 使用 Ajax 加载脚本

jQuery 的情况非常简单,只需要建立这个连接到脚本文件,当加载完成,代码便会执行。

    $.ajax({
        url : "a.js",
        type : "GET"
    });

原生 javascript 的情况稍微复杂一些,主要区别在于需要手动触发脚本。当然,在开始加载脚本之前,首先创建一个函数来获取通用的 xhr 对象:

	function getXhr() {
		xhr = false;
	    // branch for native XMLHttpRequest object
	    if(window.XMLHttpRequest && !(window.ActiveXObject)) {
	    	try {
				xhr = new XMLHttpRequest();
	        } catch(e) {
				xhr = false;
	        }
	    // branch for IE/Windows ActiveX version
	    } else if(window.ActiveXObject) {
	       	try {
	        	xhr = new ActiveXObject("Msxml2.XMLHTTP");
	      	} catch(e) {
	        	try {
	          		xhr = new ActiveXObject("Microsoft.XMLHTTP");
	        	} catch(e) {
	          		xhr = false;
	        	}
			}
	    }
		return xhr;
	}

在原生 javascript 中,第一种让脚本执行的办法是调用 eval() 函数:

	var xhr = getXhr();
	xhr.onreadystatechange = function(){
		if(xhr.readyState == 4 && xhr.status == 200){
			eval(xhr.responseText);    //执行代码
		}
	}
	xhr.open("GET","a.js",true);
	xhr.send(null);

另一个让脚本运行的方法是向页面追加元素:

	var xhr = getXhr();
	xhr.onreadystatechange = function(){
		if(xhr.readyState == 4 && xhr.status == 200){
			var script = document.createElement("script");
			var head = document.getElementsByTagName("head")[0];
			script.text = xhr.responseText;
			head.appendChild(script);
		}
	}
	xhr.open("GET","a.js",true);
	xhr.send(null);

无论是使用 jQuery,还是原生 javascript 两种方法(eval 和 追加 script 元素)中的任何一种,由于 Ajax 的同源策略,脚本只能从当前域中获取。

2. 向页面添加带 scr 属性的 script 元素

这种方法看似和直接在页面引用 script 类似,但是一个微小的区别是,使用这种方法加载脚本,不会阻塞浏览器加载和渲染其他内容,这正是我们需要的。

	var script = document.createElement("script");
	var head = document.getElementsByTagName("head")[0];
	script.src = "a.js";
	head.appendChild(script);

脚本加载完成后自动运行,获取跨域的脚本在这种方法中是被允许的。

3. script 的 defer 属性

defer 是 HTML4 中定义的 script 标签的一个属性,当浏览器检查到 script 中包含 defer 属性,这意味着告诉了浏览器,这个脚本是安全的,它不会对其他脚本造成影响,那么浏览器会并行下载该脚本,这个属性在 W3C School中文版的解释如下:

如果您的脚本不会改变文档的内容,可将 defer 属性加入到 <script> 标签中,
以便加快处理文档的速度。因为浏览器知道它将能够安全地读取文档的剩余部分而不用执行脚本,
它将推迟对脚本的解释,直到文档已经显示给用户为止。

defer 是所有方法中最简便的,但是遗憾的是,只有 IE 支持这一属性。

    <script type="text/javascript" src="a.js" defer="defer"></script>

Note: IE 8 是最早支持脚本并行下载的浏览器,之后 Firefox 等浏览器在新版本中也开始进行支持,到目前为止,较新版本的浏览器都是并行加载外部脚本文件,而无需进行额外的处理。本文中的方法则适用于所有的浏览器,当然也包括 IE6 和 IE7,同时,这些方法也适用于动态加载 js 的需要。

4 Responses to “无阻塞加载外部脚本”

  1. 头像
    可乐加糖 Reply #1

    大意都大概看懂了,对网站性能提升有帮助.还有2个方法没写啊.
    script 回写和iframe加载脚本.
    当然 iframe 是用的最少甚至是不推荐使用,就是因为其 iframe 的 dom 性能是一块诟病.
    使用 script 加载是目前比较好.
    应该是这么写的:
    var scritpObj = document.createElement("script");
    scriptObject.src="xxxx.js";
    document.getElementsByTagName("head")[0].appChild(scriptObj);

    当然,这些都涉及到脚本的拆分和规划.....

  2. 头像
    可乐加糖 Reply #2

    这些方法都涉及到服务器的脚本竞争,所以如果对脚本的加载先后顺序有要求的话,还需要进一步优化,要不然就会出现"标识符未定义"或者"xx变量未定"的错误鸟.

    ^_^ ....

  3. 头像
    fc_lamp Reply #3

    看过“node.js”没??

  4. 头像
    挪墨 Reply #4

    @可乐加糖
    iframe加载脚本,是否是在iframe里加载一段js,并暴露给外部调用,以此实现无阻塞?
    请教具体操作细节:-)

我来说两句