1.加载和运行

前记:js的出现给人们上网时的交互体验,但是一直有个地方被人们所诟病的是拖慢了网页的运行速度。
而且传统方式下,浏览器下载和运行js代码都是属于阻塞式的,会很消耗浏览器的运行时间。
为了提高网页运行速率,减少用于等待页面的时间 。很多人提出了很多种js加载和运行的方式。
以下方法出自《High Performance JavaScript》中文翻译版的《高性能JavaScript》。

基本工作原理:

1、大多数浏览器都是单线程处理UI更新和js运行等多个任务,也就说同一时间只能有一个任务被执行。

2、浏览器在遇到<script>标签时,都会停下来运行js代码,然后在进行页面解析和翻译页面。用src属性加载外部js文件时也会完全阻塞页面解析和用户交互。

3、浏览器在遇到<body>之前,不会渲染页面的任何部分。

注:目前IE8+,FF3.5+,Safari4+ , chrome2+(也就说现在(注明今年是2015年)的机会所有浏览器)都支持并行下载Javascript文件。但是JavaScript文件的下载还是会阻塞其他资源的下载,例如图片。

基于前人的工作经验,和本书中提出的方法,可以做的一些操作是:

(1)将Js脚本放在底部,</body>之前就好。(Yahoo优越性能小组关于Javascript第一条定律)

(2)减少<script>标签数。

(3)打包js文件组。

对于外部js文件,每个HTTP请求都会产生额外的性能负担。比如下载一个100KB的文件要比下载四个25KB的文件要快。

可以利用打包工具打包js文件,Yahoo提供了一个实时工具。(Yahoo!combo handler.)


非阻塞脚本

非阻塞的思路是:等页面都加载完成以后,在加载js源码。在window的load事件发生以后再开始下载代码。

(1) 延迟脚本defer

defer属性是HTML4中给出的,指明元素中所包含的脚本不打算修改DOM,代码可以稍后执行。

特性是:js文件在script被解析时下载,但代码不被执行,直到DOM都加载完成(在window的load事件句柄被调用之前执行)。

而且在下载js文件的时候,不阻塞其他处理过程,可以和页面的其他资源一起并行下载。

但是支持defer的浏览器不多。

笔者自己测试下来,只有IE(测试的是IE8)支持defer,FireFox(测试的是FF42),chrome(测试的是chrome46),Opera(测试的是Opera33),Safari(测试的是Safari5.34)都不支持
所以这个方法虽然简单,但是浏览器们都不支持啊!

(2)动态脚本元素Dynamic Script Elements(非阻塞js下载中最常用的模式)

<script>
var script = document.createElement("script");
script.src = "f1.js";

script.type = "text/javascript";
document.getElementsByTagName("head")[0].appendChild("script");
</script>

无论在何处启动下载,文件的下载和运行都不会阻塞其他页面处理过程。甚至可以将代码放在<head>内部而不会对其余部分的页面代码造成影响(除了用于下载文件的HTTP连接)。

当加载js文件采用DSE(动态脚本元素的简称)时,下载完成后返回的代码会立即执行(Opera和FireFox除外,他们会等待此前的所有动态脚本执行完毕)。所以,当返回代码是自执行代码就ok。

但是如果返回的脚本代码只是其他脚本的调用的接口,则会出现问题。(什么问题呢??待追

所以在这种情况下,需要自己去追踪脚本的处理进度(事件监听),是否已经准备好来使用。

IE浏览器可以发出readystatechange事件,还给script元素提供了readyState属性,有5个值:

  • "uninitialized":默认状态,还未开始初始化
  • "loading":下载已经开始
  • "loaded":下载完成
  • "interactive":下载完成但尚不可用
  • "complete": 所有数据都已准备好,可以使用

一旦监听到上面的5个值之一,在使用完readystatechange以后,就需要删除readystatechange事件句柄。(script.readystatechange==null,保证事件不会被处理两次)

在FireFox,Safari3+,Opera中,会发出load事件。

//兼容各个浏览的加载函数
function loadScript( url , callback )
{
   var script = document.createElement("script");
   
   script.src = "text/javascript";
   if( script.readyState )
   {  
	//IE
      script.readystatechange = funciton()
     {
         if( script.readyState == "loaded" || script.reayState == "complete")
         {
		    script.readystatechange = null;
			callback();
         }
     }
    }
	else
	{
		//非IE
		script.onload = function()
		{
		  callback();
		}
	}
   script.src = url;    
   document.getElementByTagName("head")[0].appendChild(script);
}       

 用这种方式加载js文件,浏览器不能保证文件加载顺序(Firefox和Opera除外)。

要保证文件执行顺序,可以使用嵌套的callback,但是文件过多的情况下不推荐使用。如果文件顺序很重要而且文件很多,则可以将文件打包成一个大文件。

(3)XHR脚本注入方式(大型网页不采用)

方法是:

第一:创建XHR对象注入页面中

第二:下载js文件

第三:动态创建script注入页面中

var xhr = window.XMLHTTPRequest? new window.XMLHTTPRequest():new ActiveXObject("Microsoft.XMLHTTP");
xhr.open("f1.js",'get',false);
xhr.send();
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.responseText;
        document.body.appendChild(script);
     }
  }
}  

优点:

  • 可以下载不立即执行js代码,可以推迟执行,直到一切准备好。
  • 在所有浏览器中,都不会引发异常。

缺点:

  • 无法跨域实现,js文件与页面必须在同一个域内。

该书推荐的nonblocking Pattern(非阻塞模式)

加载和运行js文件分两步:

第一步:将极必要且很小的code采用动态加载js的方式实现,主要保证加载的js内容尽量小,小到只剩下loadScript()函数。

第二步:然后加载其余的初始化js代码

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

将这段代码放置在</body>之前,好处是:不会阻塞页面其他部分的显示;当第一部分js文件loading完,所有需要的DOM节点,都已经创建完成,并准备好被访问。

可以避免额外的事件处理window.onload,来告知页面是否准备好了。

另外一种选择是,直接将loadScript函数嵌入到页面中,避免HTTP请求。(如果采用这种方式,建议使用"YUI Compressor"或类似工具将脚本压缩到最小字节尺寸)。一旦页面初始化代码下载完成,还可以使用loadScript()函数加载页面所需的额外功能函数。


其他(一些开源的插件)

(1)YUI3:由一段很小的初始化代码组成,用于下载其余的功能代码。

需要在页面中加载YUI的种子文件。(6KB,gzipped)

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


(2)The LazyLoad (精缩以后只有1.5KB)

<script type = "text/javascript" src = "lazyload-min.js"></script>
<script>
LazyLoad.js("f1.js",function(){      //多个就参数文件数组:["f1.js","f2.js"...]
    application.init();
})
</script>

能够下载多个js文件,并能够保证在所有的浏览器上按照正确顺序执行。

注:即使使用非阻塞方式下载,仍然建议较少js文件数量,因为每次下载一个文件仍然是一个单独的HTTP请求,回调函数callback()在所有文件下载并执行完成后执行。

(3)LABjs (精缩后4.5KB)(链式操作)

常用接口:

script('f1.js') : 下载f1文件

wait(fn) : 在js文件下载并执行完成以后,执行fn函数

//以这种连续方式下载f1,f2两个js文件,无法保证f1先执行。
LABjs.script("f1.js").script("f2.js").wait(function() {
   application.init(); 
});

//可以保证f2在1之后执行,但是两者是并行下载的
LABjs.script("f1.js").wait().script("f2.js").wait(function() { application.init(); });

 LABjs最大的特色就是:管理依赖关系。

其中的wait()函数可以指定哪些文件应该等待其他文件执行完在执行。

后记:以上这些纯粹都是一些理论分析,笔者还没有真正实践测试过。所谓实践出真知,等笔者学会了后台技术(捂脸。。。)再依次好好测试各种方法的优缺点及性能。
原文地址:https://www.cnblogs.com/shixiaomiao/p/5005618.html