第一章 加载和执行(Loading and Execution)读书笔记

当浏览器遇到<script>标签时,浏览器会停止处理页面,先执行JavaScript代码,然后再继续解析和渲染页面。同样的情况也发生在使用src属性加载JavaScript的过程中,浏览器必须先花时间下载外连接文件中的代码,然后解析并执行它。在这个过程中,页面渲染和用户交互完全被阻塞了[注:动态加载JavaScript除外]。

1.1 脚本位置(Script Positioning)

浏览器在解析到<body>标签之前,不会渲染页面的任何部分。把脚本放到页面顶部将会导致明显的延迟,通常表现为显示空白页面。

引用多个js文件时,每个文件必须等到前一个文件下载并执行完成才会开始下载。

IE8、Firefox3.5、Safari 4、Chrome2都允许并行下载JavaScript文件。<script>标签在下载外部资源时不会阻塞其他<script>标签,但会阻塞其他资源的下载,如图片。尽管脚本的下载过程不会互相影响,但页面仍然必须等待所有JavaScript代码下载并执行完成才能继续。【由于脚本会阻塞页面其他资源的下载,因此推荐将所有<script>标签尽可能放到<body>标签的底部,以尽量减少对整个页面的下载的影响

1.2 组织脚本(Grouping Scripts)

由于每个<script>标签初始下载时都会阻塞页面渲染,所以减少页面包含的<script>标签数量有助于改善这一情况。

提示:Steve Souders 还发现,把一段内嵌脚本放在引用外链接样式表的<link>标签之后会导致页面阻塞去等待样式表的下载。这样做是为了确保内嵌脚本再执行时能获得最精准的样式信息。因此,Souders建议永远不要把内嵌脚本紧跟在<link>标签后面

可以把多个文件合并成一个,这样只需要引用一个<script>标签。

任何网站都可以使用一个把指定文件合并处理后的URL来获取任意数量的YUI文件。如,下面URL包含两个文件

http://yui.yahooapis.com/combo?2.7.0/build/yahoo/yahoo-min.js&2.7.0/build/event/event-min.js

这个URL加载了2.7.0版本的yahoo-min.js和event-min.js文件。这两个文件在服务器上是独立存在的,但通过请求以上网址他们会被合并。原来需要用两个<script>标签分别加载两个文件,而现在用一个<script>标签即可同时加载:

<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.7.0/build/yahoo/yahoo-min.js&2.7.0/build/event/event-min.js"></script>

1.3 无阻塞的脚本(Nonblocking Scripts)

减少JavaScript文件大小

限制HTTP请求数仅仅是创建响应迅速的Web应用的第一步。

逐步加载JavaScript文件

无阻塞脚本的秘诀在于,在页面加载完成后才加载JavaScript代码。这意味着在window对象的load事件触发后再下载脚本。

1.3.1 延迟的脚本

         Deferred Scripts

         HTML 4为<script>标签定义了一个扩展属性:defer。defer属性指明本元素所含的脚本不会修改DOM,因此嗲吗能安全的延迟执行。该属性只有IE4+和Firefox 3.5+的浏览器支持,所以它不是一个理想的跨浏览器的解决方案。在其它浏览器中,defer属性会被直接忽略,因此<script>标签会以默认的方式处理(即会造成阻塞)

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

        

<html>
	<head>
		<title>Script Defer Example</title>
	</head>
	<body>
		<script defer>
			alert("defer");
		</script>
		<script>
			alert("script");
		</script>
		<script>
			window.οnlοad=function(){
				alert("load");
			};
		</script>
	</body>
</html


对应的JavaScript文件将在页面解析到<script>标签时开始下载,但不会执行,直到DOM加载完成(onload事件被触发前)。当一个带有defer属性的JavaScript文件下载时,它不会阻塞浏览器的其他进程,因此这类文件可以与页面中的其他资源并行下载。

任何带有defer属性的<script>元素在DOM完成加载之前都不会被执行,无论内嵌或外链脚本都是如此。如以上代码。

不支持defer属性的浏览器的弹出顺序是“defer”、“script” "load" 而支持defer属性的浏览器上,弹出的顺序是:“script” "defer" "load" 注意:带有defer属性的<script>元素不是跟在第二个后面执行,而是在onload事件处理器执行之前被调用

1.3.2 动态脚本元素

         Dynamic Script Elements

         这种技术的重点在于:无论在合适启动下载,文件的下载和执行过程不会阻塞页面其他进程。

         提示:通常来讲,把新创建的<script>标签添加到<head>标签里比添加到<body>里更保险,尤其是在页面加载过程中执行代码更是如此。当<body>中的内容没有全部加载完成时,IE可能会抛出一个“操作已终止”的错误信息。

         使用动态脚本节点下载文件时,返回的代码通常会立刻执行(除了Firefox和Opera,它们会等待此前所有动态脚本节点执行完毕)

         Firefox,Opera,Chrome和Safari 3以上版本会在<script>元素接收完成时触发一个load事件。可以通过侦听此事件来获得脚本加载完成时的状态。

         IE支持另外一种实现方式,它会触发一个readstatechange事件。<script>元素提供一个readyState属性,它的值在外连文件的下载过程的不同阶段会发生变化,该属性有五种取值:

         uninitialized 初始状态

         loading  开始下载

         loaded  下载完成

         interactive  数据完成下载但尚不可用

         complete  所有数据已准备就绪

         因IE在标识最终状态的readyState的值时并不一致,故最靠谱的方式是同时检查这两种状态,只要其中任何一个触发,就删除事件处理器(以确保事件不会处理两次)

         

	function loadScript(url,callback){
		var script = document.createElement("script");
		script.type = "text/javascript";
		
		if(script.readyState){//IE
			script.onreadystatechange = function(){
				if(script.readyState=="loaded"||script.readyState=="complete"){
					script.onreadystatechange = null;
					callback();
				}
			};
		}else{ //其他
			script.onload = function(){
				script.onload = null;
				callback();
			};
		}
		script.src = url;
		document.getElementsByTagName("head")[0].appendChild(script);
	}
	loadScript("test.js",function(){
		alert("File is loaded!");
	});


                  函数中使用了特征检查(Feature detection)来决定再脚本处理过程中监听哪个事件。

                  如果需要的话,可以动态加载尽可能多的JavaScript文件到页面上,但要确保考虑了文件的加载顺序。在所有主流浏览器中,只有Firefox和Opera能保证脚本会按照你指定的顺序执行,其他浏览器将会按照从服务器端返回的顺序下载和执行代码,可以将下载操作串联起来以确保下载顺序

                 

        loadScript("file1.js",function(){
			loadScript("file2.js",function(){
				loadScript("file3.js",function(){
			
			});
		});
	});

如果文件下载顺序很重要,更好的做法是把它们按正确的顺序合并成一个文件。下载这个文件就能一次获得所有代码(由于这个过程是异步的,因此文件大一点不会有影响)

1.3.3 XMLHttpRequest 脚本注入

         XMLHttpRequest Script Injection

         另一种无阻塞加载脚本的方法是使用XMLHttpRequest(XHR)对象获取脚本并注入页面中。

	var xhr = new XMLHttpRequest();
	xhr.open("get","http://localhost:20010/advert/js/pub/adv_d.js",true);
	xhr.onreadystatechange = function(){
		if(xhr.readyState==4){
			if(xhr.status>=200 && xhr.status<300 || xhr.status==304){
				var script = document.createElement("script");
				script.type = "text/javascript";
				script.text = xhr.reponseText;
				document.body.appendChild(script);
			}
		}
	};
	xhr.send(null);

2XX表示有效响应,304意味着是从缓存读取。如果收到了有效响应,就会创建一个<script>元素,设置该元素的text属性为从服务器接收到的reponseText。这实际上相当于创建一个带有内联脚本的<script>标签,一旦新创建的<script>元素被添加到页面上,代码就会立刻执行然后准备就绪。
这种方法的主要局限性是JavaScript文件必须与所请求的页面处于相同的域,这意味着JavaScript文件不能从CDN下载。因此大型的web应用通常不会采用XHR脚本注入技术。

1.3.4 推荐的无阻塞方式

         Recommended Nonblocking Pattern

         向页面中添加大量JavaScript的推荐做法只需两步:先添加动态加载所需的代码,然后加载初始化页面所需的剩下的代码。

        

<script type="text/javascript" src="loader.js"></script>
<script>
	loadScript("the-rest.js",function{
		Application.init();
	});	
</script>


把这段代码放到<body>闭合标签之前,好处:1、确保了JavaScript执行过程中不会阻塞页面其他内容的显示

                                                                         2、当第二个JavaScript文件完成下载时,应用所需的所有DOM结果已经创建完毕,并做好了交互的准备,从而避免了需要另一个事件(比如window.load)来检测页面是否准备好。

另一种方法是把loadScript()函数直接嵌入页面,从而避免多产生一次HTTP请求。此方法建议使用YUI Compressor把初始化代码压缩到最小尺寸

YUI3 的方式

         YUI3有一个核心设计理念是:由页面中的少量代码来加载丰富的功能组件。

LazyLoad类库

Yahoo!Search的工程师Ryan Grove创建了一个更为通用的延迟加载工具:LazyLoad(源代码:http://github.com/rgrove/lazyload)。LazyLoad是loadScript()函数的增强版

LazyLoad同样支持下载多个JavaScript文件,并能保证在所有浏览器中都以正常的顺序执行。

加载一个文件:

<script type="text/javascript" src="./js/lazyload-min.js"></script>
<script type="text/javascript">
	LazyLoad.js("./js/pub/adv_d.js",function(){
		alert("我被加载了");
	});
	alert("执行");
</script>

加载多个文件:

	<script type="text/javascript" src="./js/lazyload-min.js"></script>
	<script type="text/javascript">
	LazyLoad.js(["./js/pub/adv_d.js","./js/pub/adv_d123.js"],function(){
		alert("我被加载了");
	});
	alert("执行");
	</script>

要加载多个JavaScript文件,只需给LazyLoad.js()方法传入一个URL数组

        尽管使用这种无阻塞的方式可以动态加载很多文件,但建议尽量减少文件数,因为每次下载仍然是一个独立的HTTP请求,而且回调函数会等待所有文件都下载完成后才会执行。

       提示:LazyLoad同样可以动态加载CSS文件,这没有太大的意义,因为CSS文件本已是并行下载,不会阻塞页面的其他进程。

LABjs

另一个开源无阻塞脚本加载工具是Kyle Simpson受Steve Souders的启发而编写的LABjs(http://labjs.com)。改工具试图同时下载尽可能多的代码

<script type="text/javascript" src="./js/LAB.js"></script>
<script type="text/javascript">
	$LAB.script("./js/pub/adv_d.js")
		.wait(function(){
			alert("123");
		});
</script>
$LAB.script()方法用来定义需要下载的JavaScript文件,$LAB.wait()用来指定文件下载并执行完毕后所调用的函数。LABjs鼓励链式操作,因此每个方法都会返回一个$LAB对象的引用。要下载多个JavaScript文件,只需链式调用另一个$LAB.script()方法:

<script type="text/javascript" src="./js/LAB.js"></script>
<script type="text/javascript">
	$LAB.script("./js/pub/first-fire.js")
		.script("./js/pub/the_rest.js")
		.wait(function(){
			alert("123");
		});
</script>
LABjs允许你使用wait()方法来指定哪些文件需要等待其他文件。在前面的例子中,first-fire.js的代码不能保证会在the-rest.js的代码前执行。为了确保这一点,你必须在第一个script()方法后调用wait():

<script type="text/javascript" src="./js/LAB.js"></script>
<script type="text/javascript">
	$LAB.script("./js/pub/first-fire.js").wait()
		.script("./js/pub/the_rest.js")
		.wait(function(){
			alert("123");
		});
</script>

这时,尽管是并行下载,但first-file.js中的代码肯定能在the-rest.js前面执行

1.4 小结

      Sumary

      管理浏览器中的JavaScript代码是个棘手的问题,因为代码执行过程会阻塞浏览器的其他进程,比如用户界面绘制。每次遇到<script>标签,页面都必须停下来等待代码下载(如果是外链文件)并行,然后继续处理其他部分。尽管如此,还是有几种方法能减少JavaScript对性能的影响。

     <body>闭合标签之前,将所有的<script>标签放到页面底部。这能确保在脚本执行前页面已经完成了渲染。

     合并脚本。页面中的<script>标签越少,加载也就越快,响应也更迅速。无论外链文件还是内嵌脚本都是如此。

     有多种无阻塞下载JavaScript的方法:

     -使用script标签的defer属性(仅适用于IE和Firefox3.5以上版本);

     -使用动态创建的<script>元素来下载并执行代码;

     -使用XHR对象下载JavaScript代码并注入页面中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值