首页 > 代码库 > 高性能Javascript(1)

高性能Javascript(1)

第一部分 加载与运行

<html>  <head>  <title>Script Example</title>  </head>  <body>  <p>  <script type="text/javascript"> document.write("The date is " + (new Date()).toDateString()); </script>  </p>  </body> </html>

   当浏览器遇到一个<script>标签时,正如上面HTML页面中那样,无法预知JavaScript是否在<p>标签中添加内容。因此,浏览器停下来,运行此JavaScript代码,然后再继续解析、翻译页面。同样的事情发生在使用src属性加载JavaScript的过程中。浏览器必须首先下载外部文件的代码,这要占用一些时间,然后解析并运行此代码。此过程中,页面解析和用户交互是被完全阻塞的。

为了保持代码的相似性,我们尽量将相同的代码组织在一起,例如:

<html>  <head>  <title>Script Example</title> <-- Example of inefficient script positioning --> <script type="text/javascript" src="http://www.mamicode.com/file1.js"></script> <script type="text/javascript" src="http://www.mamicode.com/file2.js"></script>  <script type="text/javascript" src="http://www.mamicode.com/file3.js"></script>  <link rel="stylesheet" type="text/css" href="http://www.mamicode.com/styles.css">  </head>  <body>  <p>Hello world!</p>  </body> </html>

这些看起来比较规整的代码有一个明显的性能问题就是:在<head>部分加载了三个JavaScript文件。因为每个<script>标签阻塞了页面的解析过程,直到它完整地下载并运行了外部JavaScript代码之后,页面处理才能继续进行。用户必须忍受这种可以察觉的延迟。请记住,浏览器在遇到<body>标签之前,不会渲染页面的任何部分用这种方法把脚本放在页面的顶端,将导致一个可以察觉的延迟,通常表现为:页面打开时,首先显示为一幅空白的页面,而此时用户即不能阅读,也不能与页面进行交互操作。为了更好地理解此过程,我们使用瀑布图来描绘每个资源的下载过程。图1-1显示出页面加载过程中,每个脚本文件和样式表文件下载的过程。

第一个JavaScript文件开始下载,并阻塞了其他文件的下载过程。进一步,在file1.js下载完之后和file2.js开始下载之前有一个延时,这是file1.js完全运行所需的时间。每个
文件必须等待前一个文件下载完成并运行完之后,才能开始自己的下载过程。当这些文件下载时,用户面对一个空白的屏幕。这就是今天大多数浏览器的行为模式。Internet Explorer 8, Firefox 3.5, Safari 4, 和Chrome 2允许并行下载JavaScript文件。这个好消息表明,当一个<script>标签正在下载外部资源时,不必阻塞其他<script>标签。不幸的是,JavaScript的下载仍然要阻塞其他资源的下载过程,例如图片。即使脚本之间的下载过程互不阻塞,页面仍旧要等待所有JavaScript代码下载并执行完成之后才能继续。所以,当浏览器通过允许并行下载提高性能之后,该问题并没有完全解决。因为脚本阻塞其他页面资源的下载过程,所以推荐的办法是:将所有<script>标签放在尽可能接近<body>标签底部的位置,尽量减少对整个页面下载的影响。例如

<html>  <head>  <title>Script Example</title> <link rel="stylesheet" type="text/css" href="http://www.mamicode.com/styles.css">  </head>  <body>  <p>Hello world!</p> <-- Example of recommended script positioning --> <script type="text/javascript" src="http://www.mamicode.com/file1.js"></script> <script type="text/javascript" src="http://www.mamicode.com/file2.js"></script> <script type="text/javascript" src="http://www.mamicode.com/file3.js"></script> </body> </html>

 此代码展示了所推荐的<script>标签在HTML文件中的位置。尽管脚本下载之间互相阻塞,但页面已经下载完成并且显示在用户面前了,进入页面的速度不会显得太慢。这正是“Yahoo! 优越性能小组”关于JavaScript的第一条定律:将脚本放在底部。

另外,浏览器请求4个25K的外部JS文件的速度要慢于请求1个100K的js文件,因此,我们应该尽量将JS文件包含在一个中加载。(每个HTTP请求都会产生额外的性能负担,下载
一个100KB的文件比下载四个25KB的文件要快)。

1.1 Nonblocking Scripts 非阻塞脚本

Deferred Scripts 延期脚本

HTML 4为<script>标签定义了一个扩展属性:defer。这个defer属性指明元素中所包含的脚本不打算修改DOM,因此代码可以稍后执行。defer属性只被Internet Explorer 4和Firefox 3.5更高版本的浏览器所支持,它不是一个理想的跨浏览器解决方案。在其他浏览器上,defer属性被忽略,<script>标签按照默认方式被处理(造成阻塞)。如果浏览器支持的话,这种方法仍是一种有用的解决方案。示例如下:


<script type="text/javascript" src="http://www.mamicode.com/file1.js" defer></script>

一个带有defer属性的<script>标签可以放置在文档的任何位置。对应的JavaScript文件将在<script>被解时启动下载,但代码不会被执行,直到DOM加载完成(在onload事件句柄被调用之前)。当一个defer的JavaScript文件被下载时,它不会阻塞浏览器的其他处理过程,所以这些文件可以与页面的其他资源一起并行下载.(需要理解的是一个<script>脚本的执行时间包含两部分,(1)请求加载时间(2)JS执行时间。defer的作用是延迟JS的执行时间。任何带有defer属性的<script>元素在DOM加载完成之前不会被执行,不论是内联脚本还是外部脚本文件,都是这样。下面的例子展示了defer属性如何影响脚本行为。

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

 这些代码在页面处理过程中弹出三个对话框。如果浏览器不支持defer属性,那么弹出对话框的顺序是“defer”,“script”和“load”。如果浏览器支持defer属性,那么弹出对话框的顺序是“script”,“defer”和“load”。注意,标记为defer的<script>元素不是跟在第二个后面运行,而是在onload事件句柄处理之前被调用

Dynamic Script Elements 动态脚本元素

文档对象模型(DOM)允许你使用JavaScript动态创建HTML的几乎全部文档内容。其根本在于,<script>元素与页面其他元素没有什么不同:引用变量可以通过DOM进行检索,可以从文档中移动、删除,也可以被创建。一个新的<script>元素可以非常容易地通过标准DOM函数创建

 

var script = document.createElement ("script"); script.type = "text/javascript"; script.src = "http://www.mamicode.com/file1.js"; document.getElementsByTagName_r("head")[0].appendChild(script);

 

新的<script>元素加载file1.js源文件。此文件当元素添加到页面之后立刻开始下载。此技术的重点在于:无论在何处启动下载,文件的下载和运行都不会阻塞其他页面处理过程

你甚至可以将这些代码放在<head>部分而不会对其余部分的页面代码造成影响(除了用于下载文件的HTTP连接)

大多数情况下,你希望调用一个函数就可以实现JavaScript文件的动态加载。下面的函数封装了标准实现和IE实现所需的功能:

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 { //Others   script.onload = function(){   callback(); };  }  script.src = http://www.mamicode.com/url; "head")[0].appendChild(script); }

 1.2 XMLHttpRequest Script Injection  XHR脚本注入

另一个以非阻塞方式获得脚本的方法是使用XMLHttpRequest(XHR)对象将脚本注入到页面中。此技术首先创建一个XHR对象,然后下载JavaScript文件,接着用一个动态<script>元素将JavaScript代码注入页面。下面是一个简单的例子:

var xhr = new XMLHttpRequest(); xhr.open("get", "file1.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.responseText;  document.body.appendChild(script);  }  } }; xhr.send(null);

 此代码向服务器发送一个获取file1.js文件的GET请求。onreadystatechange事件处理函数检查readyState是不是4,然后检查HTTP状态码是不是有效(2XX表示有效的回应,304表示一个缓存响应)。如果收到了一个有效的响应,那么就创建一个新的<script>元素,将它的文本属性设置为从服务器接收到的responseText字符串。这样做实际上会创建一个带有内联代码的<script>元素。一旦新<script>元素被添加到文档,代码将被执行,并准备使用。

这种方法的主要优点是,你可以下载不立即执行的JavaScript代码。由于代码返回在<script>标签之外(换句话说不受<script>标签约束),它下载后不会自动执行,这使得你可以推迟执行,直到一切都准备好了。另一个优点是,同样的代码在所有现代浏览器中都不会引发异常。此方法最主要的限制是:JavaScript文件必须与页面放置在同一个域内,不能从CDNs下载(CDN指“内容投递网络(Content Delivery Network)”,前面002篇《成组脚本》一节提到)。正因为这个原因,大型网页通常不采用XHR脚本注入技术。

1.3 Recommended Nonblocking Pattern 推荐的非阻塞模式

推荐的向页面加载大量JavaScript的方法分为两个步骤:第一步,包含动态加载JavaScript所需的代码,然后加载页面初始化所需的除JavaScript之外的部分。这部分代码尽量小,可能只包含loadScript()函数,它下载和运行非常迅速,不会对页面造成很大干扰。当初始代码准备好之后,用它来加载其余的JavaScript。
例如:

 

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

 

 将此代码放置在body的关闭标签</body>之前。这样做有几点好处:首先,像前面讨论过的那样,这样做确保JavaScript运行不会影响页面其他部分显示。其次,当第二部分JavaScript文件完成下载,所有应用程序所必须的DOM已经创建好了,并做好被访问的准备,避免使用额外的事件处理(例如window.onload)来得知页面是否已经准备好了

1.4 The LazyLoad library && The LABjs library

 

作为一个更通用的工具,Yahoo! Search的Ryan Grove创建了LazyLoad库(参见http://github.com/rgrove/lazyload/)。LazyLoad是一个更强大的loadScript()函数。LazyLoad精缩之后只有大约1.5KB(精缩,而不是用gzip压缩的)。具体用法不再赘述,请参考详细文档说明。

另一个非阻塞JavaScript加载库是LABjs(http://labjs.com/),Kyle Simpson写的一个开源库,由Steve Souders赞助。此库对加载过程进行更精细的控制,并尝试并行下载尽可能多的代码。LABjs也相当小,只有4.50KB(精缩,而不是用gzip压缩的),所以具有最小的页面代码尺寸

Summary

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

 

[1] 将所有<script>标签放置在页面的底部,紧靠body关闭标签</body>的上方。此法可以保证页面在脚本运行之前完成解析。

[2] 将脚本成组打包。页面的<script>标签越少,页面的加载速度就越快,响应也更加迅速。不论外部脚本文件还是内联代码都是如此.

[3] 有几种方法可以使用非阻塞方式下载JavaScript:
  ——为<script>标签添加defer属性(只适用于Internet Explorer和Firefox 3.5以上版本)
  ——动态创建<script>元素,用它下载并执行代码
  ——用XHR对象下载代码,并注入到页面中

 

MORE REFERANCE

http://coolshell.cn/articles/9749.html

高性能Javascript(1)