还记得《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,而不会判断它到底会不会出错。这也说明,这个状况本身已经被那些类库所忽略了。

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

转载于:https://www.cnblogs.com/JeffreyZhao/archive/2007/01/27/Break_the_Browsers_Restrictions_5.html

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

  1. 挣脱浏览器的束缚(1) - 前言

    挣脱浏览器的束缚(1) - 前言 2007-01-18 17:08 by Jeffrey Zhao, 6129 阅读, 24 评论, 收藏, 编辑 最近在为某个人门户站点作优化. 从传统意义上来说,这 ...

  2. 挣脱浏览器的束缚(2) - 别让脚本引入坏了事

    挣脱浏览器的束缚(2) - 别让脚本引入坏了事 2007-01-20 01:25 by Jeffrey Zhao, 5267 阅读, 43 评论, 收藏, 编辑 现在哪里还找得到不引入JavaScri ...

  3. 挣脱浏览器的束缚(3) - 两个连接还不够“并行”

    挣脱浏览器的束缚(3) - 两个连接还不够"并行" 2007-01-22 14:42 by Jeffrey Zhao, 5894 阅读, 26 评论, 收藏, 编辑 在讨论这次的主 ...

  4. 挣脱浏览器的束缚(7) - CrossSubDomainExecutor

    在上次的文章中,我们已经提到了一种能够跨子域名进行AJAX请求的方法.我们现在就来实现一个对开发人员透明的实现,它会自动判断这个请求是否是跨子域名,如果不是,则使用传统的方法发出AJAX请求,反之则使 ...

  5. iOS safari浏览器上overflow: scroll元素无法滚动bug深究

    前情提要 在之前我写过一篇文章:iOS safari浏览器上overflow: scroll元素无法滑动bug解决方法整理,这篇文章写的是,当iOS safari浏览器上出现大于父容器的svg元素,想 ...

  6. 谷歌,火狐浏览器不能禁用自动补齐的bug缺陷

    IE浏览器里有autocomplete="off",可以禁止自动补全账号和密码,为了防止信息泄露,需要去除自动补齐. 自动补齐产生的场景是,form里面有密码框,因此只要将该密码框 ...

  7. onresize事件会被多次触发_如何修复移动浏览器上 touchend 事件不触发的bug

    最近做移动端一个简单的下拉刷新功能,遇到移动端浏览器touchend事件不触发的bug 监听一个 DOM 元素的 touchstart, touchmove, touchend 事件. 如果只是 to ...

  8. win10开机的微软服务器,部分 Win10 Edge 浏览器开机自动启动,微软确认是 bug

    本月初微软通过 Windows Update 将新版 MicrosoftEdge 推送给用户,并自动取代旧版 Edge.当然,如果系统原本默认的浏览器不是MicrosoftEdge,那么升级到新版后也 ...

  9. bug历程——记一次由chrome浏览器Stalled优化缓存导致的请求bug

    故事的开始是这样的 在一个月黑风高的夜晚,秋名山小白依旧在写着bug,这次他想实现的是解决重复请求问题.其实这个问题解决方案很普通,小白准备使用AOP+Redis分布式锁. 一切都很愉快的进行着,问题 ...

最新文章

  1. Swift - 经纬度位置坐标与真实地理位置相互转化
  2. 'adb' 不是内部或外部命令,也不是可运行的程序或批处理文件
  3. ProfessionalDotNetNuke 第一章(摘录)
  4. Jest 测试框架 expect 和 匹配器 matcher 的设计原理解析
  5. mac php编译freetype,Mac下本机自带PHP缺少freetype最终解决方案
  6. 玩转matlab之一维 gauss 数值积分公式及matlab源代码
  7. 《架构即未来》中最常用的15个架构原则
  8. python基础之列表生成式和生成器
  9. [译]用javascript实现一门编程语言-词法分析
  10. html 左侧居中对齐,HTML的居中对齐
  11. 【PS】106个水彩花卉和树叶画笔
  12. 下一步工作应该怎样开展
  13. 记录第一个 python项目 外星人入侵小游戏
  14. mysql中locat函数,MySQL中的LOCATE和POSITION函数使用方法
  15. 利用Java反射机制调用含数组参数的方法
  16. 【如何使用idea合并当前分支的代码到主分支】
  17. 【驱动保护】某P的内存降权
  18. 基于CTP的程序化交易系统开…
  19. 四川省工程造价咨询服务收费标准川价发2008-141
  20. Mycat2.0搭建教程

热门文章

  1. opencv 图像转换(傅里叶变换)
  2. 朴素贝叶斯(naive Bayes) 二
  3. request.post
  4. c++ eos智能合约开发_hyperledger fabric 开发第一个智能合约
  5. Matlab标准语音库 Timit Database
  6. 批量修改Dell服务器远程管理卡iDRAC密码
  7. 一个网卡配置两个不同网段的IP地址(比如应用道闸项目)
  8. Redis学习总结(23)——Redis如何实现故障自动恢复?浅析哨兵的工作原理
  9. Redis学习总结(18)——Redis 常见面试题复习
  10. Java基础学习总结(106)——高级JAVA工程师必需技能