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

之前一直在想法设法的实现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参数对于需要按顺序执行的脚本文件必不可少,用于标识当前脚本,对无需按顺序执行的脚本可有可无。

(全文…)

对长轮询Ajax调用提出的疑点

//////////////////////// 2010-02-19 update /////////////////////////////

对于实现这个轮询发出请求来更新页面UI的功能,还可以通过setInterval定时器来实现,不过普通的定时器的实现方式是在规定的时间内重复的发出请求,而不管前面的请求是否已经完成,这样的实现方式是有很多弊端的。从这点出发,我修改了通过定时器setInterval的实现方式,通过一个布尔值来判断前面的请求是否完成,没完成的话就压缩到堆栈里等待执行。下面是实现的源码:

/*
 * @author Shanpeng
 * @website:http://www.ilovejs.net
 * @e-mail:supersha@foxmail/shanpeng@taobao.com/supershafeng@gmail.com
 * @QQ:770104121
 * @my word:Here,believe what browsers display,and believe yourself
 */
////////////////////// Javascript's code below ///////////////////////
(function(global){
    var XHR = function(){
        var xhr = false;
        if (window.XMLHttpRequest) {
            xhr = new XMLHttpRequest();
        }
        else if (window.ActiveXObject("Msxml2.XMLHTTP")) {
            xhr = new ActiveXObject("Msxml2.XMLHTTP");
        }
        else {
            xhr = new ActiveXObject("Microsoft.XMLHTTP");
        }
        return xhr;
    }

    var query = [];
    var xhr = XHR();
    var ready = true;  //用于判断某个请求响应是否成功

    var queryAjax = function(url, callback){
        query[query.length] = [url, callback];
        if (ready) {
            var cur = query.shift();
            xhr.open("GET", cur[0], true);
            xhr.onreadystatechange = function(){
                if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 304)) {
                    cur[1](xhr);
                    ready = true;
                }
            }
            xhr.send(null);
        }
        ready = false;
    }
    global.queryAjax = queryAjax;
})(this);

//Usage:
onload = function(){
    var div = document.getElementById("test");
    var i = 0;
    setInterval(function(){
        queryAjax("test.txt", function(obj){
            div.innerHTML = obj.responseText + (++i);
        });
    }, 1000);
}

///////////////////////////////////////////////////////////////////

前几天接触到关于长轮询调用Ajax的技巧,原理很简单:就是在前一次Ajax调用并且响应成功之后再调用XHR对象的open和send方法,继续发送请求。代码示例如下:

var sendRequest = function(url, callback){
    var xhr = XHR();
    if (typeof xhr === "string") throw xhr;
    xhr.open("GET", url, true);
    xhr.onreadystatechange = function(){
        if (xhr.readyState === 4 && xhr.status === 200) {
            //调用回调函数处理响应
            callback(xhr);
            //重新打开连接
            xhr.open("GET", url, true);
            xhr.send(null);
        }
    }
    xhr.send(null);
}

长轮询的关键点就在onreadystatechange事件内,当响应成功之后再打开连接,发送请求。这个比起使用定时器的方式又优化很多了。定时器的代码示例如下:

var sendRequest=function(url,callback){
    var xhr=XHR();
    if(typeof xhr === "string") throw xhr;
    xhr.open("GET",url,true);
    xhr.onreadystatechange=function(){
         if(xhr.readyState === 4 && xhr.status === 200){
              callback(xhr);
         }
    }
    xhr.send(null):
}
//设置定时器
setInterval(sendRequest,1000);

定时器技巧可能会引发一个致命的问题,就是在时间间隔内如果响应还没有完成,下一个请求就又发出了,这将会导致过多的无用请求,同时也会拖慢系统的运行。

但是这篇文章并不是来说明定时器技巧和长轮询的优异,而是为了说明我在测试长轮询的过程中发现的一个问题。就拿上面第一段展示长轮询的代码来说,如果在IE8下,将会导致崩溃;Google Chrome、Safari、Opera和Firefox下正常。《测试页面

我通过将定时器跟长轮询结合起来,就可以避免这个问题,在各个浏览器下测试正常,代码示例如下:

var sendRequest = function(url, callback){
    var xhr = XHR();
    if (typeof xhr === "string")
        throw xhr;
    xhr.open("GET", url, true);
    xhr.onreadystatechange = function(){
        if (xhr.readyState === 4) {
            callback(xhr);
            //设置一个0时间间隔的定时器
            setTimeout(function(){
                xhr.open("GET", url, true);
                xhr.send(null);
            }, 0);
        }
    }
    xhr.send(null);
}

在上面的代码示例中,在onreadystatechange事件中在响应成功之后设置一个0时间间隔的定时器,这就解决了IE下的问题。《测试页面》,原理可能是定时器本身需要一个生成并执行的过程,这个就给了javascript解析器一个缓冲的过程,从而避免了“死循环”使得javascript解析器错误的解析判断代码。

在IE下崩溃的主要原因一方面是“死循环”,但是主要还是回调函数比较简单,执行的时间非常的短,使得IE的JScript解析器执行的次数超过了极限。或许回调函数如果能执行比较长时间的话,IE的这个问题也就无需使用0定时器了。

实现Ajax请求队列按顺序执行

之前想做一个Web桌面的项目,考虑的必须得使用Ajax请求队列,使得Ajax的请求能按照队列按顺序执行,解决了Ajax异步传输覆盖的问题,也看了几个别人的代码,都整不太明白,所以自己干脆自己搞搞阵,自己实现,也得个安慰奖。我的实现方法很简单,通过递归调用函数,而无需使用定时器去检查Ajax请求是否已经执行完毕,具体的例子请看下面的代码:

Javascript:

onload = function(){
    document.getElementById("btn").onclick = function(){
        //添加请求队列
        addAjax({method: "GET",url: "test2.txt",callback: callback1});
        addAjax({method: "GET",url: "test.txt",callback: callback2});
        addAjax({method: "GET",url: "test2.txt",callback: callback3});
        //开始执行队列
        executeAjax();
    }
}

var callback1 = function(data, xhr){
    document.getElementById("div1").innerHTML = data;
}
var callback2 = function(data, xhr){
    document.getElementById("div2").innerHTML = data;
}
var callback3 = function(data, xhr){
    document.getElementById("div3").innerHTML = data;
}

HTML:

<div id="div1">getStyle test.</div>
<div id="div2">getStyle test.</div>
<div id="div3">getStyle test.</div>
<input type="button" value="Get data" id="btn" />

这三个请求会按顺序执行下来,请点击查看具体效果

下面展示我所实现这个请求队列的Javascript源码:

var ajaxes = []; //用于存储参数对象的队列
//用于声明XMLHttpRequest实例对象
var Xhr = function(){
    var xhr = false;
    try {
        xhr = new XMLHttpRequest();
    }
    catch (e) {
        try {
            xhr = new ActiveXObject("Msxml2.XMLHTTP");
        }
        catch (e) {
            xhr = new ActiveXObject("Microsoft.XMLHTTP");
        }
    }
    return xhr;
}

var xhr = new Xhr(); //获得XMLHttpRequest实例对象xhr
//executeAjax是主要的执行Ajax的函数
var executeAjax = function(){
    //如果队列为空,则退出执行
    if (!ajaxes.length)
        return;
    var options = ajaxes[0];
    if (xhr) {
        xhr.open(options.method, options.url, true);
        xhr.onreadystatechange = function(){
            if (xhr.readyState === 4 &amp;&amp; (xhr.status === 200 || xhr.status === 304)) {
                options.callback(xhr.responseText, xhr);
                //删除队列中的第一个请求
                ajaxes.shift();
                //如果队列中还有请求,就接着递归执行executeAjax函数,直到队列为空
                if (ajaxes.length > 0) {
                    executeAjax();
                }
            }
        }
        if (xhr.method === "post") {
            xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        }
        xhr.send(options.data || null);
    }
}
//用于添加队列的函数
var addAjax = function(options){
    ajaxes.push(options);
}

上面写的或许有些人会认为封装性不好,污染全局变量,如果封装起来也是非常简单的,这只是本人实现了这个效果时候的草稿,放到具体的项目中当然会封装为类或者对象,方便调用,具体的封装后的代码就不提供了。

说说分支技术的一种替代方案

大家应该都知道Javascript中分支技术的实现原理:是一种用来把浏览器间的差异封装到在运行期间进行设置的动态方法中的技术。没错,这个解释是出自《Javascript设计模式》。分支技术最大的特点是只在脚本加载是一次性的确定针对特定的浏览器的代码,这样一来,在初始化完成之后,每种浏览器都只会执行针对特定浏览器的代码,能够在运行时动态确定函数代码的能力。下面是分支技术的简单实现的一个例子:

//三个对象都声明相同的createXhr方法以示统一
var SimpleXhrFactory=(function(){
   var standard={
       createXhr:function(){
            return new XMLHttpRequest();
       }
   };
   var activeXNew={
       createXhr:function(){
            return new ActiveXObject("Msxml2.XMLHTTP");
       }
   };
   var activeXOld={
      createXhr:function(){
           return new ActiveXObject("Microsoft.XMLHTTP");
      }
   };

   var testObj;
   try{
       testObj=standard.createXhr();
       return standard;
   }catch(e){
        try{
              testObj=activeXNew.createXhr();
              return activeXNew;
        }catch(e){
              testObj=activeXOld.createXhr();
              return activeXNew;
        }
   }
}();

上面的例子就是分支技术的一个应用。我不知道大家对这段代码是否有什么意见或者异议,反正我是觉得冗长了,无端端的声明了三个对象不说,最后还是需要try…catch一套。下面是我所想到的一种解决方案来代替这个关于声明XMLHttpRequest实例对象的例子:

//在运行时就确定了特定的浏览器下将要执行哪个分支的函数
var XHR = {}
XHR.create = (function(){
    if (window.XMLHttpRequest) {
        return function(){
            return new XMLHttpRequest();
        }
    }
    else {
        try {
            //为了判断Msxml2和Microsoft版本的ActiveXObject,不得不实例化一个,不过也就是一次性过了而已
            return new ActiveXObject("Msxml2.XMLHTTP") && function(){
                return new ActiveXObject("Msxml2.XMLHTTP");
            }
        } catch (e) {
            return function(){
                return new ActiveXObject("Microsoft.XMLHTTP");
            }
        }
    }
})();

上面的代码简洁明了,有一个不好的缺点就是需要在IE6下需要实例化多一个ActiveXObject(“Msxml2.XMLHTTP”)作为判断条件,IE7以上以及FF等其他浏览器则不会有这个实例化的过程,不过还是那句话,也就是在初始化的时候有这个过程而已,之后浏览器会根据特定的函数来执行。测试页面

(全文…)