高性能的JavaScript--加载和执行

写在前面

JavaScript在浏览器中的性能,可认为是开发者所要面对的最重要的可用性的问题,此问题因JavaScript的阻塞特征而复杂,也就是说JavaScript运行时其余的事情不能被浏览器处理,事实上,大多数浏览器使用单进程处理UI更新和JavaScript运行等多个任务,而同一时间只能有一个任务被执行。JavaScript运行了多长时间,那么浏览器空闲下来响应用户输入以前的等待时间就有多长。javascript

从基本层面说,这就意味着<script>标签的出现使整个页面因脚本解析、运行出现等待。不论实际的JavaScript代码是内联的仍是包含在一个不相干的外部文件中页面下载和解析过程必须停下,等待脚本完成这些处理,而后才能继续,也是页面生命周期必不可少的部分,由于脚本可能在运行过程当中修改页面内容。css

在加载JavaScript过程当中,页面解析和用户交互是被彻底阻塞的。html

脚本位置

 HTML 4 文档指出,一个<script>标签能够放在 HTML文档的 <head> 或者<body>标签中,能够在其中屡次出现。传统上,<script> 标签用于加载外部JavaScript 文件。<head>部分除此类代码外,还包含 <link>标签用于加载外部css文件和其余页面中间件。也就是说,最好把风格和行为所依赖的部分放在一块儿,首先加载他们,使他们能够获得正确的外观和行为。java

例如:浏览器

 1 <html> 
 2 <head>
 3 <title>Script Example</title>
 4 <-- Example of ineffi cient script positioning -->
 5 <script type="text/javascript" src="file1.js"></script>
 6 <script type="text/javascript" src="file2.js"></script>
 7 <script type="text/javascript" src="file3.js"></script>
 8 <link rel="stylesheet" type="text/css" href="styles.css">
 9 </head>
10 <body>
11 <p>Hello world!</p>
12 </body>
13 </html>


虽然这些代码看起来没什么问题,可是在〈head〉部分加载了三个JavaScript文件。每一个〈script〉标签阻塞了页面解析过程,直到完整的下载并运行了外部JavaScript代码以后,页面才能继续进行。在浏览器没有遇到〈body〉标签以前,不会渲染页面的任何部分。缓存

把脚本放在页面的顶端,将会致使一个能够察觉的延迟,一般表现为:页面打开一片白,用户不能阅读和操做。服务器

如图,当第一javas文件开始下载时,阻塞了其余文件下载。进一步当第一个文件下载完成以后和第二个文件下载以前有一个延时,是第一个文件彻底运行所须要的时间。网络

解决这个问题推荐的办法是:将全部<script> 标签放在尽量接近<body> 标签的底部位置,尽可能减小对整个页面下载的影响。app

如:dom

 1 <html>
 2 <head>
 3 <title>Script Example</title>
 4 <link rel="stylesheet" type="text/css" href="styles.css">
 5 </head>
 6 <body>
 7 <p>Hello world!</p>
 8 <-- Example of recommended script positioning -->
 9 <script type="text/javascript" src="file1.js"></script>
10 <script type="text/javascript" src="file2.js"></script>
11 <script type="text/javascript" src="file3.js"></script>
12 </body>
13 </html>
组成脚本

 因为每一个<script>标签下载时阻塞页面解析过程,因此限制页面的<script>总数也是能够改善性能。这个规则对内联脚本和外部脚本一样适用。每当页面解析碰到一个<script>标签时,紧接着有一段时间用于代码执行。最小化这些延迟时间能够改善页面的总体性能。

每一个HTTP请求都会产生额外的性能负担,下载一个100KB的文件比下载4个25KB的文件要快。总之,减小引用外部文件的数量。典型的,一个大型网站或者网页应用须要屡次请求JavaScript文件。你能够将这些文件整合成一个文件,只须要一个<script>标签引用,就能够减小性能损失。

非阻塞脚本

 JavaScript倾向于阻塞浏览器某些处理过程,如HTTP请求和界面刷新,这是开发者面临的最显著的性能问题。保持JavaScript文件短小,并限制HTTP请求的数量,只是建立反应迅速的网页应用的第一步。一个应用程序所包含的功能越多,所须要的JavaScript代码就越大,保持源码短小并不老是一种选择。尽量下载一个大JavaScript文件只产生一次HTTP请求。却会锁住浏览器一大段时间。为避开这种状况,你须要向页面中逐步添加JavaScript,某种程度上说不会阻塞浏览器。

非阻塞脚本的秘密在于,等页面加载以后,再加载JavaScript源码。从技术角度上讲,这意味着在window的load事件发出以后下载代码。有几种方法能够实现这种效果。

1.延期脚本

HTML4为<script>标签订义了一个扩展属性:defer。这个defer属性指明元素中所包含的脚本不打算修改DOM,所以代码能够稍后执行(适用于IE4以上浏览器)

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

带有该属性的JavaScript文件在<script>被解析时启动下载,但代码不会被执行,直到DOM加载完成,它不会阻塞浏览器的其余处理过程,因此这些文件能够与页面的其余资源一块儿并行下载。
2.动态脚本元素

文档对象模型dom容许使用JavaScript动态建立HTML的几乎所有文档内容。其根本在于<script>元素与页面其余元素没有什么不一样。
 当文件使用动态脚本节点下载时,返回的代码一般当即执行。当脚本“自运行”类型时这一机制运行正常,可是若是脚本只包含页面其余脚本调用的的接口,则会带来问题。这种状况下,你须要跟踪脚本下载完成并准备妥善的状况。
IE 会发出一个readystatechange事件。<script>元素有一个readyState属性,它的值随着外部下载的过程而改变。readyState有5种取值。
uninitialized       默认状态
loading             开始下载
interactive        下载完成但尚不可用
complete          全部数据都已经准备好

下面封装一个函数来实现JavaScript文件的动态加载:

 1 function loadScript (url, callback){
 2 var script = document.createElement ("script")
 3 script.type = "text/javascript";
 4 if (script.readyState){ //IE
 5 script.onreadystatechange = function(){
 6 if (script.readyState == "loaded" ||  script.readyState == "complete"){
 7 script.onreadystatechange = null;
 8 callback();
 9 }
10 };
11 } else {  //Others
12 script.onload = function(){
13 callback();
14 };
15 }
16 script.src = url;
17 document.getElementsByTagName_r("head")[0].appendChild(script);
18 }

使用方法:

1 loadScript("file1.js", function(){
2 alert("File is loaded!");
3 }); 

使文件按顺序加载:

1 loadScript("file1.js", function(){
2 loadScript("file2.js", function(){
3 loadScript("file3.js", function(){
4 alert("All files are loaded!");
5 });
6 });
7 });

3.XHR脚本注入
使用XMLHttpRequest(XHR)对象将脚本注入到页面中。此技术首先建立一个XHR对象,而后下载javas文件,接着用一个动态<script>元素将javas代码注入页面。

 1 var xhr = new XMLHttpRequest();
 2 xhr.open("get", "file1.js", true);
 3 xhr.onreadystatechange = function(){
 4 if (xhr.readyState == 4){
 5 if (xhr.status >= 200 && xhr.status < 300 | | xhr.status == 304){
 6 var script = document.createElement ("script");
 7 script.type = "text/javascript";
 8 script.text = xhr.responseText;
 9 document.body.appendChild(script);
10 }
11 }
12 };
13 xhr.send(null);

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

这种方法的主要优势是,您能够下载不当即执行的 JavaScript 代码。因为代码返回在<script>标签以外(换句话说不受<script>标签约束),它下载后不会自动执行,这使得您能够推迟执行,直到一切都准备好了。另外一个优势是,一样的代码在全部现代浏览器中都不会引起异常。 

此方法最主要的限制是:JavaScript 文件必须与页面放置在同一个域内,不能从 CDN 下载(CDN 指”内容投递网络(Content Delivery Network)”,因此大型网页一般不采用 XHR 脚本注入技术。

总结

减小 JavaScript 对性能的影响有如下几种方法:

  • 将全部的<script>标签放到页面底部,也就是</body>闭合标签以前,这能确保在脚本执行前页面已经完成了渲染。
  • 尽量地合并脚本。页面中的<script>标签越少,加载也就越快,响应也越迅速。不管是外链脚本仍是内嵌脚本都是如此。
  • 采用无阻塞下载 JavaScript 脚本的方法:
  • 使用<script>标签的 defer 属性(仅适用于 IE 和 Firefox 3.5 以上版本);
  • 使用动态建立的<script>元素来下载并执行代码;
  • 使用 XHR 对象下载 JavaScript 代码并注入页面中。
相关文章
相关标签/搜索