2

JavaScript在浏览区中的性能,可以认为是开发者所面临的最严重的可用性问题。 优化这个问题的第一步从它的加载和执行开始。


霸道的script标签
script标签每次出现都会霸道地让页面等待脚本的解析和执行,无论当前的JavaScript代码是内嵌还是外联,页面的下载和渲染都必须停下来等待脚本执行完毕。这是页面生存周期的必要环节,因为脚本执行过程中可能会修改页面的内容。比较典型的是document.write()与innerHTML,在JavaScript脚本解析并执行这个过程中,页面的渲染和用户交互是完全阻塞的。


脚本位置
一般情况下,我是习惯用外联方式引入js文件,并且通常将他们放在head标签上,但是现在我必须要改变这种习惯了。
这种看似正常的代码组织实际上有非常严重的性能问题:加入我在head标签内加载了两个JavaScript文件,那我的页面的渲染将等到这两个文件加载和执行完毕才开始,因为浏览器在解析body标签之前,不会渲染任何东西。在它们的加载过程中,将会导致明显的延迟,通常的表现形式为:空白页面,内容无法浏览,无法交互。
以前的浏览器加载脚本是一个加载并且执行完成之后才会去加载下一个,现在这个问题有了改善,即现在允许并行下载JavaScript脚本了,但是上述问题仍然成立。所以这里我学习一下雅虎特别性能小组提出的优化JavaScript的首要原则:将脚本放在底部!


组织脚本
1001 > 254
因为每个script标签初始下载都会阻塞页面渲染,所以我们需要减少script标签的数量来限制HTTP请求数从而改善性能,最好将多个JavaScript文件合并成一个。


无阻塞脚本
无阻塞脚本的秘诀在于:在页面加载完之后才加载JavaScript代码。这意味着在window对象的load事件触发后再下载脚本。这样做的好处是当你下载一个较大的JS文件时,浏览器还是会锁死一段时间,为了避免这种情况,我们需要向页面逐步加载JavaScript文件。

  • 延迟的脚本

script定义了一个扩展属性:defer。defer属性指明本元素所含的脚本不会修改DOM,因此代码能够安全地执行,但是浏览器的支持情况不理想。
除此之外,HTML5引入了async属性,用于异步加载脚本,asnyc与defer相同点在于都用于异步加载脚本,采用的是并行下载,在下载过程中不会产生阻塞。区别在于,asnyc是在加载完成后自动执行,而defer需要等待加载页面完成后执行,在onload事件处理器执行之前被调用。

  • 动态脚本元素

用于DOM的存在,我们可以用JavaScript动态创建HTML里面的元素,当然也包括script元素,这是动态脚本元素的大前提。
这项技术的重点在于:我通过在页面内动态创建一个script元素,并且通过给其src属性赋值来加载脚本,那么脚本会在页面内script元素被创建后才开始加载,那么无论在何时启动下载,文件的下载和执行过程不会阻塞页面的其他进程。

  • 新创建的script元素放在哪?
    通常来讲,把新创建的script标签添加到head比添加到body中更加保险,因为当body中的内容没有全部加载完成时,IE可能会抛出一个“操作已终止”的错误信息。

  • 动态创建的文件在被下载后怎样执行?
    对于Firefox,Opera,Chrome,Safari来说,会在<script>元素接收完成时触发一个load事件;但是IE不同,它触发readystatechange事件。<script>元素提供一个readyState属性,它的值在外链文件的下载过程中的不同阶段会发生不同的变化,IE这些状态有些不会全部用到,显示很混乱,最有用的就是"loaded"和"complete"两种状态;

    • "uninitialized"--初始状态

    • "loading"--开始下载

    • "loaded"--下载完成

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

    • "complete"--所有数据已经准备就绪

  • 动态脚本加载凭借它在跨浏览器兼容性和易用的优势,成为最通用无阻塞加载解决方案;

  function loadScript(url,callback){
   var script = document.createElement("script");
   script.type = "text/javascript";
   //IE
   if(script.readyState){
   script.onreadystatechange = function(){
   if(script.readyState == "loaded"||script.readyState == "complete"){
   script.onreadystatechange == null;
   callback();//回调
                        }
                }
   };
   //其他浏览器
   script.onload = function(){
   callback();
   };  
   script.src = "file.js";//可用URL取代
   //HTML5中用document.head就好了
   document.getElementByTagName("head")[0].appendChild(script); 
   }

XHMHttpRequest脚本注入
这里是另一种无阻塞脚本模式,先创建一个XHR对象,然后用它下载JavaScript文件,最后通过动态创建的<script>元素将代码注入页面中;

  var xhr = new XMLHttpRequest();
  xhr.open("open","file.js",true);
  xhr.onreadystatechange = funtion(){
  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);
                }
        }
  }

首先发送一个get请求获取file.js文件,事件处理函数onReadyStateChange检查readyState是否为4,同时校准HTTP状态码是否为有效(2XX表示有效响应,304意味着从缓存读取);

  • 优势:
    由于代码是在script标签之外返回的,因此脚本下载完成后不会自动执行,这使得你可以把脚本的执行推迟到你准备好了之后进行;

各个浏览器都支持;

  • 缺点:
    JavaScript文件必须与所请求的页面处于相同的域,这意味着JavaScript文件不能从CDN下载;


总结
向页面中添加大量javascript的推荐做法只需要两步:
先添加动态加载所需要的代码,这段代码尽量精简,甚至可以只包含loadScript()函数,
第二步就是通过调用函数来加载剩余的javascript。


对这种推荐做法也分为两种方式:
第一种是采用外联方式加载第一个javascript,并放在</body>之前,这样做的好处:确保javascript执行过程中不会阻碍页面其他内容的显示,其次,当剩下的javascript文件完成下载时,应用所需的所有DOM结构已经创建完毕,并做好了交互准备,从而避免了需要另一个事件,比如window.onload来检测页面是否准备好!
第二种方式很简单,就是将第一个javascript直接内嵌在页面,从而避免了多产生一次HTTP请求!


如果将JavaScript比作大海,那么要享受它,首先得确保它到达的是沙滩而不是悬崖,这体现在代码存放的位置,其次得让script大海若要波澜壮阔,则需要纳百川,大海的美一部分在于波浪,无阻塞模式的意义正是于此,通过有节奏地加载脚本提高JavaScript性能!


未来nan朋友
153 声望1 粉丝

developer live, programmer die!