无阻塞的脚本

为什么需要无阻塞的脚本?

  由于javascript的阻塞特性,浏览器在执行js脚本代码的时候不能做其他任何事情。简单来说,就是 <script> 标签每次出现都会让页面等待脚本的解析和执行。
  下面是两种比较简单的提高性能的做法:

  1. 在页面整个渲染过程,由于脚本会阻塞页面其他资源的下载,因此推荐将所有的<script>标签尽可能的放到<body>标签的底部。
  2. 合并脚本,减少http请求,减少每个<script> 标签初始下载,执行带来的迟延。

  减少javascript文件大小并限制HTTP请求数可以带来性能优化。但是当下载一个单个较大的javascript,虽然只产生一次HTTP请求,却会锁死浏览器一大段时间。

无阻塞的脚本

  无阻塞的脚本的思路:在页面加载完成后,才加载javascript代码。也就是说在window对象的loading时间触发后再下载脚本。

####动态脚本元素

* 通过标准 DOM 函数创建`<script>`元素  
var script = document.createElement ("script");
   script.type = "text/javascript";
   script.src = "script1.js";
   document.getElementsByTagName("head")[0].appendChild(script);

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

  1. 当文件使用动态脚本节点下载时,返回的代码通常立即执行(除了 Firefox 和 Opera,他们将等待此前的所有动态脚本节点执行完毕)。当脚本是“自运行”类型时,这一机制运行正常,但是如果脚本只包含供页面其他脚本调用调用的接口,则会带来问题。这种情况下,您需要跟踪脚本下载完成并是否准备妥善。可以使用动态 <script> 节点发出事件得到相关信息。

  Firefox、Opera, Chorme 和 Safari 3+会在<script>节点接收完成之后发出一个 onload 事件。您可以监听这一事件,以得到脚本准备好的通知:

* 通过监听 onload 事件加载 JavaScript 脚本
var script = document.createElement ("script")
script.type = "text/javascript";

//Firefox, Opera, Chrome, Safari 3+
script.onload = function(){
    alert("Script loaded!");
};

script.src = "script1.js";
document.getElementsByTagName("head")[0].appendChild(script);

  2. Internet Explorer 支持另一种实现方式,它发出一个 readystatechange 事件。<script>元素有一个 readyState 属性,它的值随着下载外部文件的过程而改变。readyState 有五种取值:

“uninitialized”:默认状态
“loading”:下载开始
“loaded”:下载完成
“interactive”:下载完成但尚不可用
“complete”:所有数据已经准备好

  “loaded”和“complete”状态。有时<script>元素会得到“loader”却从不出现“complete”,但另外一些情况下出现“complete”而用不到“loaded”。最安全的办法就是在 readystatechange 事件中检查这两种状态,并且当其中一种状态出现时,删除 readystatechange 事件句柄(保证事件不会被处理两次):

* 通过检查 readyState 状态加载 JavaScript 脚本
var script = document.createElement("script")
script.type = "text/javascript";

//Internet Explorer
script.onreadystatechange = function(){
     if (script.readyState == "loaded" || script.readyState == "complete"){
           script.onreadystatechange = null;
           alert("Script loaded.");
     }
};

script.src = "script1.js";
document.getElementsByTagName("head")[0].appendChild(script);

函数封装,兼容标准和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 = url;
    document.getElementsByTagName("head")[0].appendChild(script);
}

  此函数接收两个参数:JavaScript 文件的 URL,和一个当 JavaScript 接收完成时触发的回调函数。属性检查用于决定监视哪种事件。最后一步,设置 src 属性,并将<script>元素添加至页面。此 loadScript() 函数使用方法如下:
* loadScript()函数使用方法
loadScript(“script1.js”, function(){
alert(“File is loaded!”);
});
  您可以在页面中动态加载很多 JavaScript 文件,但要注意,浏览器不保证文件加载的顺序。所有主流浏览器之中,只有 Firefox 和 Opera 保证脚本按照您指定的顺序执行。其他浏览器将按照服务器返回它们的次序下载并运行不同的代码文件。您可以将下载操作串联在一起以保证他们的次序,如下:

* 通过 loadScript()函数加载多个 JavaScript 脚本
loadScript("script1.js", function(){
    loadScript("script2.js", function(){
        loadScript("script3.js", function(){
            alert("All files are loaded!");
        });
    });
});
此代码等待 script1.js 可用之后才开始加载 script2.js,等 script2.js 可用之后才开始加载 script3.js。

解决加载多js文件带来的“回调地狱”

  callback函数可以是去加载另外一个js,不过如果要加载的js文件较多,就成了“回调地狱”(callback hell)。
  回调地狱式可以通过一些模式来解决,例如下面给出的串行方式。

Loader = (function() {

  var load_cursor = 0;
  var load_queue;

  var loadFinished = function() {
    load_cursor ++;
    if (load_cursor < load_queue.length) {
      loadScript();
    }
  }

  function loadError (oError) {
    console.error("The script " + oError.target.src + " is not accessible.");
  }


  var loadScript = function() {
    var url = load_queue[load_cursor];
    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;
                loadFinished();
            }
        };
    } else {  //Others
        script.onload = function(){
            loadFinished();
        };
    }

    script.onerror = loadError;

    script.src = url+'?'+'time='+Date.parse(new Date());
    document.body.appendChild(script);
  };

  var loadMultiScript = function(url_array) {
    load_cursor = 0;
    load_queue = url_array;
    loadScript();
  }

  return {
    load: loadMultiScript,
  };

})();  // end Loader

//loading ...
Loader.load([
            'script1.js', 
            'script2.js',
            'script3.js'
             ]);
load_queue是一个队列,保存需要依次加载的js的url。当一个js加载完毕后,load_cursor++用来模拟出队操作,然后加载下一个脚本。

onerror事件也添加了回调,用来处理无法加载的js文件。当遇到无法加载的js文件时停止加载,剩下的文件也不会加载了。

还有许多实现动态加载js文件的方式:

https://github.com/someus/how-to-load-dynamic-script