并行加载、按顺序执行javascript文件

转载自http://www.ilovejs.net/archives/976

之前一直在想法设法的实现js脚本文件按顺序执行,而且并行下载的测试,但是都无终而返。最先设想的是通过轮询的方式来实现按顺序加载,可是这样就导致不能并行加载了。

今天又拾起这个一直未实现的冲动,首先既然要并行加载、按顺序执行,那么就必须得给每一个js文件开一个http请求,而且这个请求是无顺序的,即是加载的顺序是无顺序的,但是在执行的时候,就得要按照声明的顺序来执行,比如下面的代码示例:

jL.load({"label":"test","url":"test.js",isOrder:true});
jL.load({"label":"test2","url":"test2.js",isOrder:false});
jL.load({"label":"test3","url":"test3.js",isOrder:true,callback:function(){
  alert("test3.js is load");
}}).insert();

上面的代码中,load方法的参数中包含是否按顺序加载脚本文件,并且在脚本加载下来的时候包含callback函数回调。如果不是按顺序下载的,则直接使用DOM Element的方式插入script到document.body中;insert方法负责开始操作那些俺顺序执行的脚本文件的加载和插入工作;label参数对于需要按顺序执行的脚本文件必不可少,用于标识当前脚本,对无需按顺序执行的脚本可有可无。

 

刚开始设想的原理是这样的:把需要按顺序执行的脚本通过load方法都临时储存到一个数组里(如果不需要,则直接在load方法里通过DOM Element方法插入),之后调用insert方法来执行上面临时数组(使用XHR Injection的方式获得每一个脚本的responseText),并将请求返回的脚本的内容也暂且储存在临时变量里,待全部的脚本都请求下来之后,再把全部脚本的内容都插入到Document文档里使得脚本起作用。《测试示例1

上面的方案虽然实现了并行加载、按顺序执行,但是存在诸多弊端,如果有脚本文件比较大,请求的时间比较长,那么先于它加载下来的脚本就迟迟得不到执行了,而且在处理callback回调函数的时候也是在全部脚本加载下来以后才执行。

为此,在这个基础上,修改了实现方式:按顺序执行的脚本仍然使用XHR的方式来加载,并把内容临时储存在数组里,同时通过label标识着脚本文件添加一个load的boolean属性来标识该脚本是否已经加载完成。这样,我就可以使用定时器(setInterval)来检查按label标识顺序排好的数组来检查load属性是否为true,这样来插入脚本到文档中。这样,就可以尽早的将声明在前面的脚本在加载完成的时候就插入到文档内。《测试页面

在代码中,因为在重复调用insert方法的情况下要处理临时数组和对象的清空问题比较难解决,所以就进行了再一次的封装,支持链式调用,而且总共也才只有load和insert方法,参数说明:label用于标识该脚本文件以示区分,对于isOrder为true时labe必不可少,isOrder为false时可有可无;callback是回调函数,url为脚本的地址。操作方式如下几种:

//分类的方式,几个insert方法调用
jL().load({"label":"jquery","url":"jquery.js",isOrder:true,callback:function(){
  alert($("#test").html());
}})
.load({"label":"test","url":"test.js",isOrder:true})
.insert();
///////////////////////////////////
jL().load({"label":"test2","url":"test2.js",isOrder:true})
.load({"label":"test3","url":"test3.js",isOrder:true})
.insert();
//链式调用的方式,最后一个insert方法调用:
jL().load({"label":"jquery","url":"jquery.js",isOrder:false,callback:function(){
  alert($("#test").html());
}})
.load({"label":"test","url":"test.js",isOrder:true})
.load({"label":"test2","url":"test2.js",isOrder:true})
.load({"label":"test3","url":"test3.js",isOrder:true}).insert();
//可以将加载信息都声明到一个load方法里:
jL().load(
{"label":"jquery","url":"jquery.js",isOrder:true,callback:function(){
  alert($("#test").html());
}},
{"label":"test","url":"test.js",isOrder:true},
{"label":"test2","url":"test2.js",isOrder:true},
{"label":"test3","url":"test3.js",isOrder:true}
)
.insert();

如上所示,操作简单方便而且实用,下面是js源码:

; (function(win) {
  var _h = document.getElementsByTagName("head")[0];
  //使用lazy Definition的方式声明xhr实例
  var _xhr = function() {
    _xhr = win.XMLHttpRequest ? function() {
      return new XMLHttpRequest();
    }: (new ActiveXObject("Msxml2.XMLHTTP")) ? function() {
      return new ActiveXObject("Msxml2.XMLHTTP");
    }: function() {
      return new ActiveXObject("Microsoft.XMLHTTP");
    }
    return _xhr();
  }
  //使用DOM Element的方式把脚本动态插入到文档?
  var _domLoad = function(url, parent, callback) {
    if (!url){
      return;
    }
    var s = document.createElement("script");
    s.setAttribute("src", url); (parent || _h).appendChild(s);
    if (callback) {
      if (s.readyState) { //IE,Opera
        s.onreadystatechange = function() {
          if (s.readyState === "loaded" || s.readyState === "complete") {
            s.onreadystatechange = null;
            callback();
          }
        }
      } else { // FF,Chrome,Safari
        s.onload = function() {
          s.onload = null;
          callback();
        }
      }
    }
  }
  //通过设置script标签的text属性来插入行内脚本到文档中
  //不过一定要插入到head中,插入到body中在IE6下测试出?
  var _script = function(text) {
    var s = document.createElement("script");
    s.text = text;
    _h.appendChild(s);
  }
  //请求每一个js脚本文件
  var _request = function(_p, _o) {
    var _x = _xhr(),
    item = _o._cache[_p.label];
    _x.open("GET", _p.url, true);
    _x.onreadystatechange = function() {
      if (_x.readyState === 4 && (_x.status === 200 || _x.status === 304)) {
        item["text"] = _x.responseText;
        item["load"] = true;
      }
    }
    _x.send(null);
  }
  var _load =function (){
    for(var i=0,l=arguments.length;i<l;i++){
      var a = arguments[i];
      if(!a.isOrder){
        _domLoad(a.url,a.parent,a.callback);
      }else{
        this._param.push(a);
        this._cache[a.label]={};
        this._cache[a.label]["callback"]=a.callback;
      }
    }
    return this;
  }
  var _insert = function() {
    if (!this._param.length) {
      return;
    }
    var _o = null,i = 0;
    //请求脚本文件
    while (_o = this._param[i++]) {
      _request(_o, this);
    }
    //定时器检查并插入脚本到文档中
    var o = this._param.shift(),item = null,that = this;
    var interval = setInterval(function() {
      item = that._cache[o.label];
      if (item["load"]) {
        _script(item["text"]);
        if (item["callback"]) {
          item["callback"]();
        }
        if (! (o = that._param.shift())) {
          clearInterval(interval);
        }
      }
    },5);
    return this;
  }
  var _jL = function() {
    this._cache = {};
    this._param = [];
  }
  _jL.prototype = {
    load: _load,
    insert: _insert
  }
  win.jL = function() {
    return new _jL();
  }
})(this);

posted @ 2010-11-12 15:35  nuage  阅读(1138)  评论(0编辑  收藏  举报