Hello World
Spiga

挣脱浏览器的束缚(5) - 哭笑不得的IE Bug

2007-01-27 03:27 by 老赵, 6591 visits

还记得《ASP.NET AJAX Under the Hood Secrets》吗?这是我在自己的Blog上推荐过的唯一一篇文章(不过更可能是一时兴起)。在这片文章里,Omar Al Zabir提出了他在使用ASP.NET AJAX中的一些经验。其中提到的一点就是:Browsers do not respond when more than two calls are in queue。简单的说,就是在IE中,如果同时建立了超过2两个连接在“连接状态”中,但是没有连接成功(连接成功之后就没有问题了,即使在传输数据),浏览器会停止对其他操作的响应,例如点击超级链接进行页面跳转,直到除了正在尝试的两个连接就没有其他连接时,浏览器才会重新响应用户操作。

出现这个问题一般需要3个条件:

  • 同时建立太多连接,例如一个门户上有许多个模块,它们在同时请求服务器端数据。
  • 响应比较慢,从浏览器发起连接,到服务器端响应连接,所花的时间比较长。
  • 使用IE浏览器,无论IE6还是IE7都会这个问题,而FireFox则一切正常。

在IE7里居然还有这个bug,真是令人哭笑不得。但是我们必须解决这个问题,不是吗?

 

编写代码来维护一个队列

与《ASP.NET AJAX Under the Hood Secrets》一文中一样,最容易想到的解决方案就是编写代码来维护一个队列。这个队列非常容易编写,代码如下:

if (!window.Global)
{
    window.Global = new Object();
}

Global._RequestQueue = function()
{
    this._requestDelegateQueue = new Array();
    
    this._requestInProgress = 0;
    
    this._maxConcurrentRequest = 2;
}

Global._RequestQueue.prototype =
{
    enqueueRequestDelegate : function(requestDelegate)
    {
        this._requestDelegateQueue.push(requestDelegate);
        this._request();
    },
    
    next : function()
    {
        this._requestInProgress --;
        this._request();
    },
    
    _request : function()
    {
        if (this._requestDelegateQueue.length <= 0) return;
        if (this._requestInProgress >= this._maxConcurrentRequest) return;
        
        this._requestInProgress ++;
        var requestDelegate = this._requestDelegateQueue.shift();
        requestDelegate.call(null);
    }
}

Global.RequestQueue = new Global._RequestQueue();

 

我在实现这个队列时使用了最基本的JavaScript,可以让这个实现不依赖于任何AJAX类库。这个实现非常容易实现的,我简单介绍一下它的使用方式。

  1. 在需要发起AJAX请求时,不能直接调用最后的方法来发起请求。需要封装一个delegate然后放入队列。
  2. 在AJAX请求完成时,调用next方法,可以发起队列中的其他请求。

例如,我们在使用prototype 1.4.0版时我们可以这样:

<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>Request Queue</title>
    <script type="text/javascript" src="js/prototype-1.4.0.js"></script>
    <script type="text/javascript" src="js/RequestQueue.js"></script>
    
    <script language="javascript" type="text/javascript">
        function requestWithoutQueue()
        {
            for (var i = 0; i < 10; i++)
            {
                new Ajax.Request(
                    url,
                    {
                        method: 'post',
                        onComplete: callback
                    });
            }
            
            function callback(xmlHttpRequest)
            {
                ...
            }
        }
        
        function requestWithQueue()
        {
            for (var i = 0; i < 10; i++)
            {
                var requestDelegate = function()
                {
                    new Ajax.Request(
                        url,
                        {
                            method: 'post',
                            onComplete: callback,
                            onFailure: Global.RequestQueue.next,
                            onException: Global.RequestQueue.next
                        });
                }
                
                Global.RequestQueue.enqueueRequestDelegate(requestDelegate);
            }
            
            function callback(xmlHttpRequest)
            {
                ...
                Global.RequestQueue.next();
            }
        }
    </script>
</head>
<body>
    ...
</body>
</html>

 

在上面的代码中,requestWithoutQueue方法发起了普通的请求,requestWithQueue则使用了Request Queue,大家可以比较一下它们的区别。

 

使用Request Queue的缺陷

这个Request Queue能够工作正常,但是使用起来实在不方便。为什么?

我们来想一下,如果一个应用已经写的差不多了,我们现在需要在页面里使用这个Request Queue,我们需要怎么做?我们需要修改所有发起请求的地方,改成使用Request Queue的代码,也就是建立一个Request Delegate。而且,我们需要把握所有的异常情况,保证在出现错误时,Global.RequestQueue.next方法也能够被及时地调用。否则这个队列就无法正常工作了。还有,ASP.NET AJAX中有UpdatePanel,该怎么建立Request Delegate?该如何访问Global.RequestQueue.next方法?

我们该怎么办?

 

可怜的JavaScript,太容易受骗了

我们需要找出一种方式,能够轻易的用在已有的应用中,解决已有应用中的问题。怎么样才能让已有应用修改尽可能的少呢?我们来想一个最极端的情况:一行代码都不用改,这可能么?

似乎是可能的,我们只需要骗过JavaScript就可以。可怜的JavaScript,太容易骗了。话不多说,直接来看代码,一目了然:

window._progIDs = [ 'Msxml2.XMLHTTP', 'Microsoft.XMLHTTP' ];

if (!window.XMLHttpRequest)
{
    window.XMLHttpRequest = function()
    {
        for (var i = 0; i < window._progIDs.length; i++)
        {
            try
            {
                var xmlHttp = new _originalActiveXObject(window._progIDs[i]);
                return xmlHttp;
            }
            catch (ex) {}
        }
        
        return null;
    }
}

if (window.ActiveXObject)
{    
    window._originalActiveXObject = window.ActiveXObject;

    window.ActiveXObject = function(id)
    {
        id = id.toUpperCase();
        
        for (var i = 0; i < window._progIDs.length; i++)
        {
            if (id === window._progIDs[i].toUpperCase())
            {
                return new XMLHttpRequest();
            }
        }
        
        return new _originaActiveXObject(id);
    }
}

window._originalXMLHttpRequest = window.XMLHttpRequest;

window.XMLHttpRequest = function()
{
    this._xmlHttpRequest = new _originalXMLHttpRequest();
    this.readyState = this._xmlHttpRequest.readyState;
    this._xmlHttpRequest.onreadystatechange = 
        this._createDelegate(this, this._internalOnReadyStateChange);
}

window.XMLHttpRequest.prototype = 
{
    open : function(method, url, async)
    {
        this._xmlHttpRequest.open(method, url, async);
        this.readyState = this._xmlHttpRequest.readyState;
    },
    
    send : function(body)
    {
        var requestDelegate = this._createDelegate(
            this,
            function()
            {
                this._xmlHttpRequest.send(body);
                this.readyState = this._xmlHttpRequest.readyState;
            });
        
        Global.RequestQueue.enqueueRequestDelegate(requestDelegate);
    },
    
    setRequestHeader : function(header, value)
    {
        this._xmlHttpRequest.setRequestHeader(header, value);
    },
    
    getResponseHeader : function(header)
    {
        return this._xmlHttpRequest.getResponseHeader(header);
    },
    
    getAllResponseHeaders : function()
    {
        return this._xmlHttpRequest.getAllResponseHeaders();
    },
    
    abort : function()
    {
        this._xmlHttpRequest.abort();
    },
    
    _internalOnReadyStateChange : function()
    {
        var xmlHttpRequest = this._xmlHttpRequest;
        
        try
        {
            this.readyState = xmlHttpRequest.readyState;
            this.responseText = xmlHttpRequest.responseText;
            this.responseXML = xmlHttpRequest.responseXML;
            this.statusText = xmlHttpRequest.statusText;
            this.status = xmlHttpRequest.status;
        }
        catch(e){}
        
        if (4 === this.readyState)
        {
            Global.RequestQueue.next();
        }
        
        if (this.onreadystatechange)
        {
            this.onreadystatechange.call(null);
        }
    },
    
    _createDelegate : function(instance, method)
    {
        return function()
        {
            return method.apply(instance, arguments);
        }
    }
}

 

本来在想出这个解决方案时,我心中还比较忐忑,担心这个方法的可行性。当真正完成时,可真是欣喜不已。这个解决方案的的关键就在于“伪造JavaScript对象”。JavaScript只会直接根据代码来使用对象,我们如果将一些原生对象保留起来,并且提供一个同名的对象。这样,JavaScript就会使用你提供的伪造的JavaScript对象了。在上面的代码中,主要伪造了两个对象:

  • window.XMLHttpRequest对象:我们将XMLHttpRequest原生对象保留为window._originalXMLHttpRequest,并且提供一个新的(或者说是伪造的)window.XMLHttpRequest类型。在新的XMLHttpRequest对象中,我们封装了一个原生的XMLHttpRequest对象,同时也会定义了XMLHttpRequest原生对象存在的所有方法和属性,大多数的方法都会委托给原生XMLHttpRequest对象(例如abort方法)。需要注意的是,我们在新的XMLHttpRequest类型的send方法中,创造了一个delegate放入了队列中,并且_internalOnReadyStateChange方法在合适的情况下(readyState为4,表示completed)调用Global.RequestQueue.next方法,然后再触发onreadystatechange的handler。
  • ActiveXObject对象:由于类库在创建XMLHttpRequest对象的实现不同,有的类库会首先使用ActiveX进行尝试(例如prototype),有些则会首先尝试window.XMLHttpRequest对象(例如Yahoo! UI Library),因此我们必须保证在通过ActiveX创建XMLHttpRequest对象时也能够使用我们伪造的window.XMLHttpRequest类。实现相当的简单:保留原有的window.ActiveXObject对象,在通过新的window.ActiveXObject创建对象时判断传入的id是否为XMLHttpRequest所需的id,如果是,则返回伪造的window.XMLHttpRequest对象,否则则使用原来的ActiveXObject(保存在window._originaActiveXObject变量里)创建所需的ActiveX控件。

其实“骗取”JavaScript的“信任”非常简单,这也就是JavaScript灵活的体现,我们在扩展一个JS类库时,我们完全可以想一下,是否能够使用一些“巧妙”的办法来改变原有的逻辑呢?

 

“伪造”XMLHttpRequest对象的优点与缺点

现在,要在已有的应用中修改浏览器僵死的状况则太容易了,只需在IE浏览器中引入RequestQueue.js和FakeXMLHttpRequest.js即可。而且我们只需要把“判断”浏览器类型的任务交给浏览器本身就行了,如下:

<!--[if IE]>
    <script type="text/javascript" src="js/RequestQueue.js"></script>
    <script type="text/javascript" src="js/FakeXMLHttpRequest.js"></script>
<![endif]-->

 

这样,只有在IE浏览器中,这两个文件才会被下载,何其容易!

那么,这么做会有什么缺点呢?可能最大的缺点,就是伪造的对象无法完全模拟XMLHttpRequest的“行为”。如果在服务器完全无法响应时,访问XMLHttpRequest的status则会抛出异常。请注意,这里说的“完全无法响应”不是指Service Unavailable(很明显,它的status是503),而是彻底的访问不到,比如机器的网络连接断了。而在伪造的XMLHttpRequest中,status无法模拟一个方法调用(IE没有FireFox里的__setter__),因此无法抛出异常。

这个问题很严重吗?个人认为没有什么问题。看看常见的类库封装,都是直接访问status,而不会判断它到底会不会出错。这也说明,这个状况本身已经被那些类库所忽略了。

那么我们也忽略一下吧,这个解决方案还是比较让人满意的。至少目前看来,在使用过程中没有出现问题。我们的“欺骗”行为没有被揭穿,异常成功。:)

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

77 条回复

  1. 老赵
    admin
    链接

    老赵 2007-01-27 03:33:00

    上面的代码使用了框架无关的实现,能够轻松地兼容各种JavaScript类库。完全可以直接拿过来使用,当时写完之后也非常的高兴,蛮有成就感的。:)

  2. Clingingboy
    *.*.*.*
    链接

    Clingingboy 2007-01-27 03:56:00

    这么晚了还不睡觉啊,别太累

  3. 老赵
    admin
    链接

    老赵 2007-01-27 04:00:00

    @Clingingboy
    周末,睡得再早还是中午起,那么不如睡晚些。:)

  4. 怪怪[未注册用户]
    *.*.*.*
    链接

    怪怪[未注册用户] 2007-01-27 04:30:00

    无话可说,只能直接Save As了 :)

  5. 老赵
    admin
    链接

    老赵 2007-01-27 04:49:00

    @怪怪
    呵呵,觉得有个可以直接用的solution是非常重要的。

  6. xx
    *.*.*.*
    链接

    xx 2007-01-27 08:35:00

    那这样的话,指向多域名的多联接请求技巧岂不成了鸡肋?如果不做队列处理,那么浏览器极有可能在一个请求死掉的情况下造成页面瘫痪,什么也干不了,直到页面所有请求全部请求完毕,只剩死掉的请求。老赵我这样理解对吗?

  7. 虫子[未注册用户]
    *.*.*.*
    链接

    虫子[未注册用户] 2007-01-27 08:37:00

    长见识了.

  8. 一滴水
    *.*.*.*
    链接

    一滴水 2007-01-27 09:02:00

    学习了!

  9. 怪怪[未注册用户]
    *.*.*.*
    链接

    怪怪[未注册用户] 2007-01-27 10:07:00

    @Jeffrey Zhao
    请教个问题,自己琢磨了一夜,没找到问题所在...

    DragDrop在IE6的效率怎么那么低?

    看看你对这块有研究没,不行就只好无耻地去PageFlakes或者Live.com扒他们用的代码了:),反正我以前用过的代码改动到Atlas也得费很多功夫..

  10. 哈密瓜牌牛奶
    *.*.*.*
    链接

    哈密瓜牌牛奶 2007-01-27 10:46:00

    @Jeffrey Zhao
    晕死,怎么赵大哥这么晚睡觉啊,不好的习惯哦。

  11. A.Z
    *.*.*.*
    链接

    A.Z 2007-01-27 11:04:00

    Cool

  12. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2007-01-27 12:24:00

    @怪怪
    Beta1开始就这样,因为prototype方式对比原来的enclosure方式书写的类在IE6的执行效率会降低,而在FF的执行效率会略为提高,这一降低就导致DragDrop响应非常迟钝了。

  13. ※ABeen※
    *.*.*.*
    链接

    ※ABeen※ 2007-01-27 12:34:00

    不错,学习中...

  14. 老赵
    admin
    链接

    老赵 2007-01-27 13:27:00

    @xx
    通过实验,似乎只有在XMLHttpRequest发起连接时才出现这个问题。
    而且,即使下载图片也有这个问题,这个做法也不能说是鸡肋啊。多域名的话下载速度会加快,这个问题就会很快消失了。

  15. 老赵
    admin
    链接

    老赵 2007-01-27 13:28:00

    @哈密瓜牌牛奶
    习惯了,这样时间就多出来了。:)

  16. 老赵
    admin
    链接

    老赵 2007-01-27 13:28:00

    @怪怪
    IE对于JS的执行效率的确不好,因此还是非常需要注意尽量提高代码性能的。

  17. 老赵
    admin
    链接

    老赵 2007-01-27 13:31:00

    @Cat Chen
    嗯,不过其实不会差太多。
    使用prototype方式的话,在IE中执行效率会略有下降,创建对象效率会提高,内存占有量会减少。在FireFox中,执行效率差不多,创建对象效率会提高,内存占有量也会减少。
    其实prototype在性能上的优势主要体现在“创建对象”上,而不是“执行对象”方法上。
    IE性能低倒的确是IE本身的问题,IE6里这个问题很严重,IE7里好了不少。

  18. 老赵
    admin
    链接

    老赵 2007-01-27 13:32:00

    @※ABeen※
    @A.Z
    @一滴水
    @虫子
    谢谢。:)

  19. S.Sams
    *.*.*.*
    链接

    S.Sams 2007-01-27 13:45:00

    这个问题其实挺严重的, 搞得我的流程设计得改进.

  20. 老赵
    admin
    链接

    老赵 2007-01-27 13:46:00

    @S.Sams
    为什么这么说呢?您的流程是什么样的呢?

  21. 魏晋遗疯[匿名][未注册用户]
    *.*.*.*
    链接

    魏晋遗疯[匿名][未注册用户] 2007-01-27 23:04:00

    老赵,这个问题会不会也能解决那个Iframe进度条一直显示的问题啊

  22. 老赵
    admin
    链接

    老赵 2007-01-28 00:02:00

    @魏晋遗疯[匿名]
    应该不会……尝试一下?呵呵。

  23. firedlove[未注册用户]
    *.*.*.*
    链接

    firedlove[未注册用户] 2007-01-28 12:59:00

    我调用你JS代码后有个错误
    this._xmlHttpRequest.open()
    对象不支持此属性或方法

  24. 老赵
    admin
    链接

    老赵 2007-01-28 13:31:00

    @firedlove
    请问您是如何使用它的呢?
    能够发一个重现问题的示例给我吗?:)

  25. firedlove[未注册用户]
    *.*.*.*
    链接

    firedlove[未注册用户] 2007-01-28 14:39:00

    项目几个JSF
    function InitRequest()
    {
    var C_req = null;
    try
    {
    C_req = new ActiveXObject("Msxml2.XMLHTTP");
    }
    catch(e)
    {
    try{C_req = new ActiveXObject("Microsoft.XMLHTTP");}
    catch(oc){C_req = null;}
    }
    if (!C_req && typeof XMLHttpRequest != "undefined")
    {
    try{C_req = new XMLHttpRequest();}
    catch(fa){alert("对不起!您的浏览器不支持该功能,请使用Internet Explorer 6.0或FireFox浏览器!");C_req = null;}
    }
    return C_req;
    }
    function PostRequest(url, data)
    {
    var AjaxRequestObj = InitRequest();
    if (AjaxRequestObj != null)
    {
    AjaxRequestObj.onreadystatechange = function ()
    {
    if (AjaxRequestObj.readyState == 4 && AjaxRequestObj.responseText)
    {
    ProcessAjaxData(AjaxRequestObj.responseText);
    }
    };
    AjaxRequestObj.open("POST", url, true);
    AjaxRequestObj.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
    AjaxRequestObj.send(data);
    }
    }
    function ProcessAjaxData(data)
    {
    eval(data);
    }

  26. firedlove[未注册用户]
    *.*.*.*
    链接

    firedlove[未注册用户] 2007-01-28 14:41:00

    其他都是调用这几个JS Function 然后我把你上面的代码复制出来 引用到项目里来 发现有JS错误
    虚心求教下 怎么解决

  27. firedlove[未注册用户]
    *.*.*.*
    链接

    firedlove[未注册用户] 2007-01-28 14:46:00

    我开了JS调试说这个
    this._xmlHttpRequest.open
    undefined

  28. 老赵
    admin
    链接

    老赵 2007-01-28 20:45:00

    @firedlove
    我用您的代码运行过了,没有任何问题啊。

  29. 怪怪[未注册用户]
    *.*.*.*
    链接

    怪怪[未注册用户] 2007-01-28 20:51:00

    @Jeffrey Zhao
    @Cat Chen
    这个我知道,Live、PageFlakes的脚本还没看,都是closures的? 不太可能吧。过去用过一套拖动的脚本用老外的一篇文章改的,也是基于protype的,就没有这么卡。所以我估计还是某一个过程耗费太多,无论是不是基于核心JS库中的问题,就是想找出来绕过去就好了。可惜js没有工具可以测试。其实这部分倒没必要非得基于Atlas的库,只是如果以后主要基于Atlas开发,我个人特喜欢统一的风格...

  30. 老赵
    admin
    链接

    老赵 2007-01-28 21:40:00

    @怪怪
    其实应该说是privilege吧?应该都通过prototype扩展才合理。估计还是算法的问题,导致计算太多。

  31. firedlove[未注册用户]
    *.*.*.*
    链接

    firedlove[未注册用户] 2007-01-28 22:26:00

    this._xmlHttpRequest
    {...}
    onreadystatechange: {...}
    好象没有OPEN方法 我再研究下看看出什么错误了

  32. firedlove[未注册用户]
    *.*.*.*
    链接

    firedlove[未注册用户] 2007-01-28 22:30:00

    能发个简单的例子给我吗?
    我看看是不是其他的JS影响了
    email:firedlove@163.com
    人笨 大哥辛苦下

  33. firedlove[未注册用户]
    *.*.*.*
    链接

    firedlove[未注册用户] 2007-01-28 22:37:00

    页面我是这么写的 通过这个调用一个页面加载数据
    function loadingAlbumCreate()
    {
    var objName = 'Ajax_AlbumCreater';
    var reloadFun ='loadingAlbumCreate()';
    document.getElementById(objName).innerHTML='<div class="loading">正在加载,请稍候,如长时间没反应,请<a href="javascript:'+reloadFun+'">刷新</a>...</div>';
    var data = "UserName="+ StrCode('<%=CurrentAlbum.UserName %>');
    var url = path+"Ajax/AlbumContent/AlbumCreater.aspx";
    loadingContent(objName,data,url);
    }
    loadingAlbumCreate();

    加载数据封装
    function loadingContent(objName,data,url)
    {
    var AjaxRequestObj = InitRequest();
    if (AjaxRequestObj != null)
    {
    AjaxRequestObj.onreadystatechange = function ()
    {
    if (AjaxRequestObj.readyState == 4 && AjaxRequestObj.responseText)
    {
    document.getElementById(objName).innerHTML= AjaxRequestObj.responseText;
    }
    };
    AjaxRequestObj.open("POST", url, true);
    AjaxRequestObj.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
    AjaxRequestObj.send(data);
    }
    }

    一个页面我有几快地方要加载 会出现你说的问题 我把你的2个JS放上去后出现对象不支持该属性 好象那个OPEN方法没出现 我看了下你的JS 也不知道哪里出问题

  34. 老赵
    admin
    链接

    老赵 2007-01-28 22:55:00

    @firedlove
    已发,请查收。:)

  35. 老赵
    admin
    链接

    老赵 2007-01-28 22:58:00

    @firedlove
    其实没有从您的代码中看出什么问题来……
    this._xmlHttpRequest是原生的XMLHttpRequest对象,open方法一定是有的。

  36. firedlove[未注册用户]
    *.*.*.*
    链接

    firedlove[未注册用户] 2007-01-28 23:21:00

    我叫别人也试了下 好象有的行有的不行 规律好象是打了微软最新补丁的好象可以
    不知道其他人有试过没
    我回了份邮件 我把我错误截图了 好奇怪的IE浏览器

  37. 老赵
    admin
    链接

    老赵 2007-01-28 23:37:00

    @firedlove
    阿哈?好奇怪的现象……

  38. firedlove[未注册用户]
    *.*.*.*
    链接

    firedlove[未注册用户] 2007-01-28 23:40:00

    @Jeffrey Zhao
    看来不通用 如果要拿来用 估计还是一个个函数改 不然有人跑不了

  39. 老赵
    admin
    链接

    老赵 2007-01-28 23:46:00

    @firedlove
    不应该。
    this_xmlHttpRequest是浏览器的XMLHttpRequest原生对象,不可能没有open的。

  40. firedlove[未注册用户]
    *.*.*.*
    链接

    firedlove[未注册用户] 2007-01-28 23:55:00

    但我这里一直没有OPEN 只有个onreadystatechange 我也找不到哪里出问题

  41. 老赵
    admin
    链接

    老赵 2007-01-28 23:57:00

    @firedlove
    我过一会儿检查一下,暂时没有时间。:)

  42. firedlove[未注册用户]
    *.*.*.*
    链接

    firedlove[未注册用户] 2007-01-29 00:01:00

    @Jeffrey Zhao
    我继续关注 期待你的好消息

  43. 老赵
    admin
    链接

    老赵 2007-01-29 00:31:00

    @firedlove
    能告诉我您用了什么版本的浏览器吗?

  44. firedlove[未注册用户]
    *.*.*.*
    链接

    firedlove[未注册用户] 2007-01-29 00:48:00

    @Jeffrey Zhao
    版本号6.0.2900.2180.xpsp_sp2_rtm.040803-2158

  45. 老赵
    admin
    链接

    老赵 2007-01-29 00:59:00

    @firedlove
    能不能提供一些成功的情况和失败的情况的浏览器版本信息吗?
    麻烦了,谢谢!

  46. 老赵
    admin
    链接

    老赵 2007-01-29 15:10:00

    啊哈,这篇文章的代码有个拼写错误,已经改过来了。:)

    之前的一个originalActiveXObject少拼了个l。:(

  47. zxub[未注册用户]
    *.*.*.*
    链接

    zxub[未注册用户] 2007-04-10 17:23:00

    伪造的时候,可以只创建任务记录对象,加入队列中,队列执行时,获取真正的xmlhttprequest对象,执行任务。
    也就是只有真正做事的时候,才去取一个实际的xmlhttprequest对象,伪造的对象完全不需要模拟XMLHttpRequest,而是委托一个实际的XMLHttpRequest去完成任务,这样就完美了。

  48. 老赵
    admin
    链接

    老赵 2007-04-10 18:06:00

    @zxub
    这样就做不到“神不知鬼不觉”了,赫赫。
    我现在这个解决方案的好处在于,完全只需引用这个脚本即可,无需在任何地方作改动。

  49. zxub[未注册用户]
    *.*.*.*
    链接

    zxub[未注册用户] 2007-04-11 09:52:00

      完全可以无侵入实现的,而且伪造后,实际的XMLHttpRequest对象由任务记录对象调用,编码中操作的实际只是任务记录对象。
      执行任务记录对象的send方法时,是将自己压入队列中。队列执行到该任务记录对象的时候,才获取实际xmlhttprequest对象,根据记录信息,给实际的请求对象赋属性的。
      说白了,就是先只缓存请求信息,延迟发送真正的请求,鉴于用的是委托模式,自己完全不做实事,最后你提到的缺点,也不存在了。

  50. zxub[未注册用户]
    *.*.*.*
    链接

    zxub[未注册用户] 2007-04-11 09:59:00

    我是做了一个FakeXMLHttpRequest类,通过伪造,根据原来的代码产生的实际都是FakeXMLHttpRequest对象,而该对象开始只保存请求的相关信息,执行send方法的时候,将自己压入任务队列。队列执行时,取出一个FakeXMLHttpRequest对象,并获取一个真正的XMLHttpRequest对象,根据FakeXMLHttpRequest的相关信息,发出一次真正的请求。
    由于可以复用,从头到尾可以只有2个XMLHttpRequest对象,我是缓存到数组中的。

  51. 老赵
    admin
    链接

    老赵 2007-04-11 12:27:00

    @zxub
    我想了一下我的实现和您的实现的区别,发现除了所谓的复用2个XMLHttpRequest对象之外就可谓完全一样了吧。缓存这个感觉没有必要,我说的问题还是存在的。
    您可以给我看一下您的代码吗?:)

  52. zxub[未注册用户]
    *.*.*.*
    链接

    zxub[未注册用户] 2007-04-11 14:39:00

  53. zxub[未注册用户]
    *.*.*.*
    链接

    zxub[未注册用户] 2007-04-11 14:56:00

    空白的话,把浏览器的编码设置成UTF-8

  54. 老赵
    admin
    链接

    老赵 2007-04-11 16:06:00

    @zxub
    简单地看了一下,你也没有解决访问status时抛出异常的情况,这就是我的实现中唯一的缺点吧?
    看到您的整个实现感觉太复杂了,还有您有些地方比如“强加时间戳”的必要性不大。:)

  55. zxub[未注册用户]
    *.*.*.*
    链接

    zxub[未注册用户] 2007-04-11 16:13:00

    访问status时抛出异常转交到onreadystatechange中去处理,本身并不做事,只是负责缓存状态信息。如果onreadystatechange中都不能处理,那就是httprequest对象的事了。

    强加时间戳是一定要的,很多地方不会自己去禁缓存。

    这个本身也是个工具类,可以直接使用的,还有不少地方要修改。

  56. zxub[未注册用户]
    *.*.*.*
    链接

    zxub[未注册用户] 2007-04-11 16:37:00

    摆弄这个东西学到不少,万分感谢^_^

  57. 老赵
    admin
    链接

    老赵 2007-04-11 17:11:00

    @zxub
    多多交流。:)

  58. 老赵
    admin
    链接

    老赵 2007-04-11 17:11:00

    @zxub
    因为加了时间戳就改变了原有XMLHttpRequest的行为了,有些应用就是要利用这个缓存呢?其实IE在GET时应用缓存也是合理的,“禁止缓存”的加时间戳的方法,应该交给用户去完成。我认为既然我们既然是不希望别人察觉有任何修改的话,还是保留浏览器原来的行为比较好。:)

  59. zxub[未注册用户]
    *.*.*.*
    链接

    zxub[未注册用户] 2007-04-12 00:24:00

    刚看完碟,突然想到个问题。
    如果直接给你个真正的request对象,怎么判断status出错问题。
    如果可以解决,那通过伪造,把请求任务指定给实际request对象,并已经进入其onreadystatechange,判断status出错不是一样的么。
    如果不能解决,由于直接用原生对象都不行,那用包装后的对象不是也一样不行么。

  60. 老赵
    admin
    链接

    老赵 2007-04-12 00:34:00

    @zxub
    访问status时可能出错,是原生对象的特性。我们无法在用户访问我们对象的status时出现错误。您的意思我倒没有听懂,不知道您听懂我的意思了吗?:)

  61. 老赵
    admin
    链接

    老赵 2007-04-12 00:35:00

    @Jeffrey Zhao
    我们对象的方法可以委托给原生对象的方法,可是我们无法控制用户直接访问status时的行为。

  62. zxub[未注册用户]
    *.*.*.*
    链接

    zxub[未注册用户] 2007-04-12 08:45:00

    经过包装后,用户访问包装对象的status之前,已经由包装对象访问了原生对象的status。
    访问原生对象的status出错的话,能否捕捉,不能就根本没有办法了,而我们也不需要再考虑这个问题。
    假设有某种出错表现形式的话,访问了原生对象的status的时候,应该已经出现了。

  63. zxub[未注册用户]
    *.*.*.*
    链接

    zxub[未注册用户] 2007-04-12 08:51:00

    用户访问包装对象的status,是在onreadystatechange中的,如果网络问题,原生对象的onreadystatechange有否反应?如果连在原生对象的onreadystatechange中都不能处理的话,我们也一样不用去处理了。

  64. zxub[未注册用户]
    *.*.*.*
    链接

    zxub[未注册用户] 2007-04-12 08:56:00

    包装对象将原生对象的属性做了缓冲,如果缓冲的时候不出问题,那转到引用包装对象的属性的时候也不该有问题,如果有问题,在前面也应该有反应了。

  65. zxub[未注册用户]
    *.*.*.*
    链接

    zxub[未注册用户] 2007-04-12 08:58:00

    这里的关键应该就在那个try...catch那,我们是把它的错误忽略掉了的。

  66. zxub[未注册用户]
    *.*.*.*
    链接

    zxub[未注册用户] 2007-04-12 09:22:00

    我改了一下实现,去掉属性赋值那个try..catch,只有原生对象的readyState为4的时候,才将原生对象的属性赋给包装对象,然后再转入用户所写的回调中,副作用就是用户写的回调只会执行一次了^_^

  67. 老赵
    admin
    链接

    老赵 2007-04-12 11:00:00

    @zxub
    我们当然可以捕捉访问status时出错的问题,但是我们无法再新建的XHR里“模拟”出这种情况。我们现在的目的就是尽可能地和原来的对象表现一致,可惜这一点做不到。

  68. 老赵
    admin
    链接

    老赵 2007-04-12 11:02:00

    @zxub
    onreadystatechange有反应。
    不过即使没有反应,理论上用户也可以访问那个对象的status,我们现在是要尽可能保证行为是相同的。所以我们就光不能考虑“正常情况下”的用法。
    当然我文章里就说了,status这个问题其实根本不影响使用。

  69. 老赵
    admin
    链接

    老赵 2007-04-12 11:03:00

    @zxub
    缓冲原生对象的意义实在不大,说真的。
    至于try...catch,我就是为了防止访问status时出错的情况才加的。

  70. 老赵
    admin
    链接

    老赵 2007-04-12 11:06:00

    @zxub
    “只有原生对象的readyState为4的时候”
    这个就完全错误了,因为这个和原生对象的行为相差太远了。我们应该是在任何用户能够访问对象的情况下都保证行为的一致。
    如果您这么做了,打个比方吧,您的代码就不能和prototype框架一起使用了,因为它还会在readyState改变时触发事件。

  71. zxub[未注册用户]
    *.*.*.*
    链接

    zxub[未注册用户] 2007-04-12 12:02:00

    我说的是缓冲原生对象的属性(就是赋属性值readyState、status、responseText等给包装对象),而非缓冲原生对象,这是另一个问题,不用管它。

    至于那个prototype框架的问题,我倒是忽略了,完全把非正常状况给屏蔽了,受教,把“只有原生对象的readyState为4的时候”去掉就行了。

    我想说的是,status只有readyState在有限的几种情况下才能取值,而如果取值赋给包装对象的时候已经出错,那大可直接将错误throw,传递给包装对象,根本就不会等到用户再取包装对象的status了。尽管中间被我们拦截了,可能改变或附加了一些行为,但我想这样对于实际应用是可以的。这在您的代码中也是可以实现的,当然忽略它也没什么。

    这种钻牛角尖的感觉也还不错^_^

  72. 老赵
    admin
    链接

    老赵 2007-04-12 12:10:00

    @zxub
    嗯,我现在是更加希望伪造的对象能够更加和原生对象的行为相似,反正尽可能做到吧。

  73. zxub[未注册用户]
    *.*.*.*
    链接

    zxub[未注册用户] 2007-04-12 13:16:00

    只要将错误传递过去就行啦,这是能做到的。
    自己写的对象毕竟是不能监听的,充其量只能玩玩AOP,做到这份上已经够了。
    写这个加深了apply的用法,不错,3q,以后还请多多指教。
    貌似问题也讨论到头了^_^
    最后说一句,很精简的代码,good!以后我会关注这里的,希望能看更多更好的文章^_^

  74. 老赵
    admin
    链接

    老赵 2007-04-12 13:33:00

    @zxub
    赫赫,其实不一样,因为我们的要求是要避免用户的代码作任何修改,所以要尽可能保证和之前的行为完全相同,否则用户不是要修改他的代码了吗?:)
    欢迎多多讨论。:)

  75. zxub[未注册用户]
    *.*.*.*
    链接

    zxub[未注册用户] 2007-04-12 16:05:00

    用户不用改代码的,多余工作由我们来做,否则就失去写这个东西的意义了。

    刚查过prototype1.5的代码,里面对于request对象有个setTimeout操作,所以不能缓存原生对象了,我以后也不缓存了,确实没这个必要,再弄个setTimeout把原生对象delete就行了,交给自动垃圾回收去做也行^_^

  76. 892734982739482384[未注册用户…
    *.*.*.*
    链接

    892734982739482384[未注册用户] 2009-06-23 10:13:00

    请问ie 缺少浏览器端时间戳怎么解决

  77. shell
    117.88.91.*
    链接

    shell 2012-06-25 01:18:59

    我的观点是,IE卡就让他卡吧,用户知道IE的差劲表现了,哪天用了其它先进的浏览器,就不会再愿意用IE了。 这么fix fix IE何时是个头。。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我