前端优化-Javascript篇(2.异步加载剧本)

  上篇博客说过剧本后置能够使页面更快的加载,然则如许的优化照样有限的,假如剧本须要实行一个耗时的操纵,就算后置了它照样会壅塞后续剧本加载和实行而且壅塞全部页面。下面引见非壅塞加载剧本手艺也就是异步加载。

非壅塞加载剧本

1.defer(关于defer的一篇好文)
  如今一切浏览器都支撑defer属性,然则Chrome和Firefox中只需在加载外部剧本时defer才会见效,行内剧本运用defer是没有作用的。而IE中不论什么状况,defer都有效。
  defer的作用就是阻挠剧本在下载完成后马上实行,它会让剧本延晚到一切剧本加载实行完成后,在DOMContentLoaded之前实行,浅显的说就是递次加载耽误实行。虽然都是在DOMContentLoaded之前实行,然则在差别浏览器之间,实行的种种剧本实行的递次照样不一样的。看下面这个例子:

<html>
  <meta charset="utf-8">
  <head>
    <script type="text/javascript">
      var result = "" ;
      var head = document.getElementsByTagName("head")[0] ;
      //DOMContentLoaded
      if(window.addEventListener){
        document.addEventListener("DOMContentLoaded",function(){
          result += "DOMContentLoaded\n" ;
        }) ;
      }else{
        document.attachEvent("onDOMContentLoaded",function(){
          result += "DOMContentLoaded\n" ;
        }) ;
      }
      window.onload = function(){
          result += "window loaded\n";
          //console.log("window loaded") ;
      } ;
    </script>
    <!--头部行内耽误剧本-->
    <script type="text/javascript" defer = "defer">
      result += "Head Inline Script defer\n" ;
    </script>
    <!--头部行内剧本-->
    <script type="text/javascript">
      result += "Head Inline Script\n" ;
    </script>
    <!--头部外部耽误剧本 External Head Script defer-->
    <script type="text/javascript" src = "external_head_defer.js" defer="defer"></script>
    <!--头部行内剧本 External Head Script-->
    <script type="text/javascript" src = "external_head.js"></script>
  </head>
  <body>
    <button>SHOW</button>
    <!--Body行内耽误剧本-->  
    <script type="text/javascript" defer = "defer">
      result += "Body Inline Script defer\n" ;
    </script>
    <!--Body行内剧本-->
    <script type="text/javascript">
      result += "Body Inline Script\n" ;
    </script>
    <!--Body外部耽误剧本 External Body Script defer-->
    <script type="text/javascript" defer = "defer" src = "external_body_defer.js"></script>
    <!--Body外部剧本 External Body Script-->
    <script type="text/javascript" src = "external_body.js"></script>
    <script type="text/javascript">
      document.getElementsByTagName("button")[0].onclick = function(){console.log(result);} ;
    </script>
  </body>
</html>

运转效果以下:
《前端优化-Javascript篇(2.异步加载剧本)》
从上面能够看出几个题目:
  起首,IE9以下不支撑DOMContentLoaded(背面会申明这个状况)
  其次,考证了上面说的Chrome和Firefox行内剧本不支撑defer属性
  末了,defer确切达到了耽误实行的目标,没有壅塞背面剧本的加载和实行。然则耗时的操纵照样会壅塞DOMContentLoaded事宜,而大多数状况下人人都邑把页面初始化的剧本附加在DOMContentLoaded事宜上,所以defer要领照样不能很好处置惩罚这个题目。

2.Script DOM
  这是最经常使用也是如今广泛的处置惩罚要领。它只须要简朴几句话就能够完成剧本的异步加载,而且一切浏览器都支撑这个要领。然则在每一个浏览器中,实行照样略有差别。看下面这个例子:

<html>
  <meta charset="utf-8">
  <head>
    <script type="text/javascript">
      var result = "\n" ;
      var head = document.getElementsByTagName("head")[0] ;
      //DOMContentLoaded
      if(window.addEventListener){
        document.addEventListener("DOMContentLoaded",function(){
          alert("DOMContentLoaded") ;
          result += "DOMContentLoaded\n" ;
        }) ;
      }else{
        document.attachEvent("onDOMContentLoaded",function(){
          alert("DOMContentLoaded") ;
          result += "DOMContentLoaded\n" ;
        }) ;
      }
      window.onload = function(){
          result += "window loaded\n";
      } ;
    </script>
    <!--头部外部耽误剧本 External Head Script defer-->
    <script type="text/javascript" src = "external_head_defer.js" defer="defer"></script>
    <!--头部行内剧本 External Head Script-->
    <script type="text/javascript" src = "external_head.js"></script>
  </head>
  <body>
    <button>SHOW</button>
    <script type="text/javascript">
      document.getElementsByTagName("button")[0].onclick = function(){console.log(result);} ;
    </script>
    <script type="text/javascript">
      result += "start\n" ;
      var head = document.getElementsByTagName("head")[0] ;
      var script8 = document.createElement("script") ;
      script8.type = "text/javascript" ;
      script8.onload = function(){alert("done");} ;
      script8.readystatechange = function(){
          if(script8.readyState == "loaded" || script8.readyState == "complete"){
              alert("done") ;
          }
      } ;
      //Body Dynamic Script
      script8.src = "dynamic_body.js" ;
      head.appendChild(script8) ;
      result += "end\n" ;
    </script>    
  </body>
</html>

运转效果以下:
《前端优化-Javascript篇(2.异步加载剧本)》
  下面这张图是在ScriptDom剧本背面到场一个耗时的剧本,使得这个剧本实行完成后,保证ScriptDOM的剧本处于可实行状况:

<script type="text/javascript">
    function doSomething(length){
        var start = new Date().getTime() ;
        while((new Date().getTime() - start) < 1000 * length){}
    }
    doSomething(3) ;
</script>  

效果以下:
《前端优化-Javascript篇(2.异步加载剧本)》
运转效果同时也申清楚明了几个题目:
  起首,ScriptDOM不会壅塞后续剧本的实行,依据start和end 的位置能够很轻易看出。
  其次,在第二张图的状况下,ScriptDOM和defer同时都能够实行,在差别浏览器中它们的优先级的不一样的。在Firfox和Chrome中,ScriptDOM的优先级比defer低,而在IE中状况则相反。
  末了,经由过程两种状况的对照发明,在Chrome中ScriptDOM不会壅塞DOMContentLoaded事宜然则会壅塞onload事宜;在Firefox中ScriptDOM既会壅塞DOMContentLoaded事宜也会壅塞onload事宜;而在IE中,状况则要依据代码实行状况来决议。假如在DOMContentLoaded事宜或许onload事宜触发之前,ScriptDOM代码处于可实行状况,那末就会壅塞两个事宜;假如在DOMContentLoaded事宜或许onload事宜触发之前,ScriptDOM代码处于不可实行状况,那末就不会壅塞两个事宜。总结的来讲就是在Chrome和IE中DOMContentLoaded事宜不须要守候ScriptDOM实行,而在Firefox中须要守候ScriptDOM实行。

  经由过程上面两种要领的对照发明,defer和ScriptDOM都不会壅塞后续剧本的实行。然则相对来讲,ScriptDOM在运用上越发天真而且并不老是壅塞DOMContentLoaded事宜,而且ScriptDOM的运用场景主如果在按需加载和模块加载器上,而平常运用这些手艺的时刻,页面已处于加载完成的状况,所以关于机能不会有影响。
  

DOMContentLoaded

  上面说到DOMContentLoaded事宜,DOMcontentLoaded是当代浏览器才支撑的一个事宜,万恶的IE从IE9最先才支撑这个事宜。那末在什么状况下才会触发DOMContentLoaded事宜呢?DOMContentLoaded会在浏览器接收到服务器传过来的HTML文档,全部页面DOM构造加载完成而且一切行内剧本和外部剧本实行完成后触发 (经由过程上面异步剧本的例子能够看出,ScriptDOM异步加载剧本不会壅塞DOMContentLoaded,或许说DOMContentLoaded不须要守候ScriptDOM实行就能够动身) ,它跟onload事宜的区别是,DOMContentLoaded事宜不须要守候图片,ifram和款式表等资本加载完成就会触发,而onload事宜须要守候全部页面都加载完成包含种种资本才会触发。所以关于我们来讲DOMContentLoaded是一个更有效的事宜,因为只需DOM构造加载完成,我们就能够经由过程Javasscript来操纵页面上的DOM节点。
  然则上面关于DOMContentLoaded事宜触发前提的定义只是官方文档的说法,具体状况并不老是如许。
  偶然款式表的加载会壅塞剧本的实行从而壅塞DOMContentLoaded事宜,这类状况平常出如今款式表背面随着剧本。也就是说假如把剧本放在款式表背面,那末剧本就必须比及款式表加载完成才最先实行,如许就会壅塞页面的DOMContentLoaded事宜。然则如许做也是有原理的,因为偶然刻我们的剧本会处置惩罚DOM款式方面的东西。
  这类壅塞状况在差别浏览器上表现也会不一样。在IE和Firefox中,不论款式表背面随着是行内剧本照样外部剧本,都邑发作壅塞。在Chrome中,只需外部剧本才会发作壅塞。
  因为IE在IE9以下不支撑DOMContentLoaded事宜,所以我们须要用一些Hack手艺来完成这个功用。分两种状况来完成:
  1.网页不嵌套在iframe中
  在IE中我们能够经由过程一个体式格局来推断DOM是不是加载完成,就是doScroll要领。假如DOM加载完成,那末我们就能够挪用document的doScroll要领,不然就会抛出非常。我们能够应用这个特征不停轮询来做Hack。

    function bindReady(handle){
        //推断是不是在iframe中
        try{
            var isFrame = window.frameElement != null ;
        }catch(e){}
        if(document.documentElement.doScroll && !isFrame){
            //轮询是不是能够挪用doScroll要领
            function tryScroll(){
                try{
                    document.documentElement.doScroll("left");
                    handle() ;
                }catch(e){
                    setTimeout(tryScroll,10) ;
                }
            }
            tryScroll() ;
        }
    }

  2.网页嵌套在iframe中
  假如网页嵌套在iframe中,那末是没法经由过程doScroll的要领来Hack完成DOMContentLoaded的。我们能够经由过程别的一种体式格局来完成—readystatechange,代码以下:

    function bindReady(handle){
        document.onreadystatechange = function(){
            if(document.readyState === "complete" || document.readyState === "loaded"){
                handle() ;
            }
        }
    }

  连系上面的议论,我们能够得出一个通用的bindReady要领。

//绑定DOMContentLoaded事宜,支撑绑定多个处置惩罚函数
var handleList = [] ;
function onReady(handle){
    //按递次实行处置惩罚函数
    var doHandles = function(){
        var length = handleList.length ;
        for(var i = 0 ; i < length ; i ++){
            handleList[i]() ;
        }
    }
    if(handleList.length == 0){
        //在还没有处置惩罚函数时,把doHandles注册到ready上,如许背面到场的处置惩罚函数就能够一并实行
        bindReady(doHandles) ;
    }
    //把处置惩罚函数到场到函数列表中
    handleList.push(handle) ;
}
function bindReady(handle){
    var called = false ;
    var ready = function(){
        //防备反复挪用
        if(!called){
            called = true ;
            handle() ;
        }
    }
    if(document.addEventListener){
        //支撑DOMcontentLoaded
        document.addEventListener("DOMContentLoaded",ready,false);
    }else if(document.attachEvent){
        //IE
        try{
            var isFrame = window.frameElement != null ;
        }catch(e){}
        //网页不在iframe中
        if(document.documentElement.doScroll && !isFrame){
            function tryScroll(){
                try{
                    document.documentElement.doScroll("left") ;
                    ready() ;
                }catch(e){
                    setTimeout(tryScroll,10) ;
                }
            }
            tryScroll() ;
        }else{
            //网页在iframe中
            document.onreadystatechange = function(){
                if(document.readyState === "complete" || document.readyState === "loaded"){
                    ready() ;
                }
            }
        }
    }
    //老式浏览器不支撑上面两种事宜
    if(window.addEventListener){
        window.addEventListener("load",ready,false) ;
    }else if(window.attachEvent){
        window.attachEvent("onload",ready) ;
    }else{
        //许可绑定多个处置惩罚函数
        var fn = window.onload ;
        window.onload = function(){
            fn && fn() ;
            ready() ;
        }
    }
}

说在末了

  说了这么多,虽然经由过程剧本后置和异步加载能够下降剧本加载对页面的影响,然则就算是完成了异步加载,然则因为浏览器的剧本剖析的单线程的,所以剧本实行的时刻仍然会壅塞全部页面(固然除了运用Web Worker),这时刻用户是没法完成一般交互的,所以要想真正完全的优化页面加载,还须要从代码的优化最先。从下一篇最先,我会分享关于这方面的进修。

末了,安利下我的个人博客,迎接接见: http://bin-playground.top

    原文作者:lakb248
    原文地址: https://segmentfault.com/a/1190000000529967
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞