不同javascript加载方式对页面性能的影响

来源:互联网 发布:linux 内核 内存管理 编辑:程序博客网 时间:2024/05/19 05:38

很多人都知道在一个html文档中JavaScript脚本应该放在<body>标签结束之前,这样页面的性能会明显提高,但具体原因什么呢?

首先从浏览器的工作原理来分析,当浏览器的渲染引擎通过网络获取所请求的文档内容后就开始解析html,并将标签转化为文档树中的dom节点,以构建dom树,接着解析外部的CSS文件及style标签中的样式信息,这些样式信息以及html中的可见元素将被用来构建render树,(render树由一些有颜色和大小等的属性的矩形构成,他们将按照正确的顺序被显示到屏幕上)接下来是布局render树,也就是将确定每个节点在屏幕上的确切坐标,最后就是绘制render树,即遍历render树,并使用UI后端层绘制每个节点,把内容呈现给用户。

在解析html的过程中一旦遇到<script>,页面的下载和渲染都会停止直到脚本执行完成,为什么会这样?

因为浏览器无法预知请求的脚本是否会改变DOM树,或直接跳转页面,所以所有工作都必须停下来直到脚本执行完成,这也就是为什么JavaScript脚本会阻塞页面的原因。假如我们把脚本放在<head>里面,那么用户将面对白屏很长一段时间,因为页面在解析到body之前用户什么也看不到的。

之前有过疑问为什么样式表的下载和解析就不会阻塞页面呢?

这是因为渲染引擎不会等到所有的html下载解析完才构造和布局render树,两者是并行的,解析完一小部分内容就显示一小部分内容。并且样式信息的主要是和根据DOM树来构建渲染树,它只可能改变一个元素的样式,并不会对DOM树造成任何影响,所以页面无需停下来等到所有的样式表解析完成再进行下载和渲染,后面解析到新的样式只需对前面的样式进行重绘就可以了。

但是在一个特殊的情况下样式表也会阻塞页面,就是在外链样式表的<link>标签后面内嵌脚本时会导致页面阻塞去等待样式表的下载,这是浏览器为了确保内嵌脚本在执行时能获得最精准的样式信息。

怎样增加页面性能减少脚本阻塞给页面带来的影响呢?

也许大家会想到减少HTTP请求来减少阻塞的次数,的确下载一个100k的文件会比下载425k的文件快,但是假如脚本特别大,那不就意味着用户要一次等待很久的时间,这样可能很多用户会失去耐心,就好一件东西一次性付款要给10元,大家觉得很贵,分期付款。每次给5毛分30给,大家会觉得哇这个东西很便宜,可以接受。所以当文件过大时应该将其分成几部分,先让用户看到点东西。

有一种更好的方法时在页面加载完成(即window对象的load事件出触发后)后再加载JavaScript代码,实现零阻塞,实现这一效果的方式有以下几种。

1、延迟脚本
即在
<script>标签中使用defer属性指明该元素所含的脚本不会修改DOM,因为代码可以安全地延迟执行。但是目前只有部分浏览器支持该属性。

我们可以进行测试,代码如下:

测试1:不延迟脚本执行

<!DOCTYPE html><meta charset="utf-8"><html><head>  <title>不延迟脚本</title></head><body><script type="text/javascript">  var a = [];  for(var i = 100000;i--;){    a[i] = i;  }</script>没有延迟脚本的</body></html>


测试2:延迟脚本执行

<!DOCTYPE html><meta charset="utf-8"><html><head>  <title>延迟脚本</title></head><body><script type="text/javascript" defer>  var a = [];  for(var i = 100000;i--;){    a[i] = i;  }</script>延迟脚本的</body></html>


从下面的截图可以看到延迟执行脚本的页面加载时间是未延迟脚本页面加载时间的一半。

不延迟脚本执行:


延迟脚本执行:


2、动态脚本元素
即原先的通过动态创建
<script>节点将脚本添加到页面中,采用该方法无论在何时启用下载,文件在下载和执行的过程中均不会阻塞页面其他进程,但是需要注意的是采用该方法加载脚本使,脚本一旦下载完就会立刻执行(除了FirefoxOpera),并不会按照加载的顺序等待前面的执行完再执行,所以假如加载的几个脚本文件之间存在一定的依赖关系就可能会出现错误,所以如果后一个脚本依赖于前一个脚本必须在前一个脚本加载完之后才可以加载,并且动态加载的脚本最好是添加到<head>标签里面,否则在IE浏览器中当<body>的内容没有加载完时可能会出现错误。
例子:

测试1:延迟脚本执行

<!DOCTYPE html><meta charset="utf-8"><html><head>  <title>延迟脚本</title></head><body>延迟脚本的<script type="text/javascript">  var script = document.createElement("script");  script.type = "text/javascript";  script.src = "lib/javascript/jquery-1.7.2.min.js";document.getElementsByName("head")[0].appendChild(script);</script></body></html>测试2:不延迟脚本执行<!DOCTYPE html><meta charset="utf-8"><html><head>  <title>不延迟脚本</title></head><body>没有延迟脚本的<script type="text/javascript" src="lib/javascript/jquery-1.7.2.min.js"></script></body></html>


从下面的截图可以看到延迟执行脚本的页面加载时间是未延迟脚本页面加载时间相差特别多。

不延迟脚本执行:


延迟脚本执行:


假如脚本文件中存在依赖关系可以使用下面这个方法函数来解决(已经兼容IE浏览器):

 

/**   * 动态加载脚本函数   * @param url 要加载的脚本路径   * @param callback  回调函数   */function loadScript(url,callback){    var script = document.createElement("script");    script.type = "text/javascript";    if(script.readyState){  //IE      script.onreadystatechange = function(){        if(script.readyState=="load"||script.readyState=="complete"){          script.onreadystatechange = null;          callback();        }      };    }else{      script.onload = function(){        callback();      };    }    script.src = "lib/javascript/jquery-1.7.2.min.js";    document.getElementsByTagName("head")[0].appendChild(script);  }

使用方法:串联起来确保加载顺序

loadScript("files1.js",function(){   loadScript("files2.js",function(){      alert("All Load !");   })});


3、XMLHTTPRequest脚本注入
使用该方法跟上面介绍的动态脚本元素的方法有类似地方,通过
AJAX异步下载一个JavaScript脚本文件,将从服务器接受到的responText通过创建动态的<script>元素将代码注入页面,这实际上相当于创建一个带有内联脚本的<script>标签,一旦创建的<script>元素被添加到页面,代码就会立刻执行并准备被就绪。
该方法与第二种方法比较的优点是,
JavaScript代码下载完不会立刻执行,不用担心脚本间的依赖关系,但是局限性在于JavaScript文件必须与所请求的页面处于形同的域。
例子:

不延迟脚本执行:


延迟脚本执行:


总结:

向页面中添加大量的JavaScript的推荐做法只需要两步:

1、先添加动态加载所需的代码

2、加载初始化页面所需的剩下的代码。

可以将loadScript函数封装完引入,如下:

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


另一种方法时直接将loadScript函数直接嵌入页面,从而避免一次HTTP请求,但如果采用这种做法,最好使用YUI Compressor将初始化代码压缩到尽可能小。