挣脱浏览器的束缚(5) - 哭笑不得的IE Bug
还记得《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类库。这个实现非常容易实现的,我简单介绍一下它的使用方式。
- 在需要发起AJAX请求时,不能直接调用最后的方法来发起请求。需要封装一个delegate然后放入队列。
- 在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) - 前言 2007-01-18 17:08 by Jeffrey Zhao, 6129 阅读, 24 评论, 收藏, 编辑 最近在为某个人门户站点作优化. 从传统意义上来说,这 ...
- 挣脱浏览器的束缚(2) - 别让脚本引入坏了事
挣脱浏览器的束缚(2) - 别让脚本引入坏了事 2007-01-20 01:25 by Jeffrey Zhao, 5267 阅读, 43 评论, 收藏, 编辑 现在哪里还找得到不引入JavaScri ...
- 挣脱浏览器的束缚(3) - 两个连接还不够“并行”
挣脱浏览器的束缚(3) - 两个连接还不够"并行" 2007-01-22 14:42 by Jeffrey Zhao, 5894 阅读, 26 评论, 收藏, 编辑 在讨论这次的主 ...
- 挣脱浏览器的束缚(7) - CrossSubDomainExecutor
在上次的文章中,我们已经提到了一种能够跨子域名进行AJAX请求的方法.我们现在就来实现一个对开发人员透明的实现,它会自动判断这个请求是否是跨子域名,如果不是,则使用传统的方法发出AJAX请求,反之则使 ...
- iOS safari浏览器上overflow: scroll元素无法滚动bug深究
前情提要 在之前我写过一篇文章:iOS safari浏览器上overflow: scroll元素无法滑动bug解决方法整理,这篇文章写的是,当iOS safari浏览器上出现大于父容器的svg元素,想 ...
- 谷歌,火狐浏览器不能禁用自动补齐的bug缺陷
IE浏览器里有autocomplete="off",可以禁止自动补全账号和密码,为了防止信息泄露,需要去除自动补齐. 自动补齐产生的场景是,form里面有密码框,因此只要将该密码框 ...
- onresize事件会被多次触发_如何修复移动浏览器上 touchend 事件不触发的bug
最近做移动端一个简单的下拉刷新功能,遇到移动端浏览器touchend事件不触发的bug 监听一个 DOM 元素的 touchstart, touchmove, touchend 事件. 如果只是 to ...
- win10开机的微软服务器,部分 Win10 Edge 浏览器开机自动启动,微软确认是 bug
本月初微软通过 Windows Update 将新版 MicrosoftEdge 推送给用户,并自动取代旧版 Edge.当然,如果系统原本默认的浏览器不是MicrosoftEdge,那么升级到新版后也 ...
- bug历程——记一次由chrome浏览器Stalled优化缓存导致的请求bug
故事的开始是这样的 在一个月黑风高的夜晚,秋名山小白依旧在写着bug,这次他想实现的是解决重复请求问题.其实这个问题解决方案很普通,小白准备使用AOP+Redis分布式锁. 一切都很愉快的进行着,问题 ...
最新文章
- Swift - 经纬度位置坐标与真实地理位置相互转化
- 'adb' 不是内部或外部命令,也不是可运行的程序或批处理文件
- ProfessionalDotNetNuke 第一章(摘录)
- Jest 测试框架 expect 和 匹配器 matcher 的设计原理解析
- mac php编译freetype,Mac下本机自带PHP缺少freetype最终解决方案
- 玩转matlab之一维 gauss 数值积分公式及matlab源代码
- 《架构即未来》中最常用的15个架构原则
- python基础之列表生成式和生成器
- [译]用javascript实现一门编程语言-词法分析
- html 左侧居中对齐,HTML的居中对齐
- 【PS】106个水彩花卉和树叶画笔
- 下一步工作应该怎样开展
- 记录第一个 python项目 外星人入侵小游戏
- mysql中locat函数,MySQL中的LOCATE和POSITION函数使用方法
- 利用Java反射机制调用含数组参数的方法
- 【如何使用idea合并当前分支的代码到主分支】
- 【驱动保护】某P的内存降权
- 基于CTP的程序化交易系统开…
- 四川省工程造价咨询服务收费标准川价发2008-141
- Mycat2.0搭建教程
热门文章
- opencv 图像转换(傅里叶变换)
- 朴素贝叶斯(naive Bayes) 二
- request.post
- c++ eos智能合约开发_hyperledger fabric 开发第一个智能合约
- Matlab标准语音库 Timit Database
- 批量修改Dell服务器远程管理卡iDRAC密码
- 一个网卡配置两个不同网段的IP地址(比如应用道闸项目)
- Redis学习总结(23)——Redis如何实现故障自动恢复?浅析哨兵的工作原理
- Redis学习总结(18)——Redis 常见面试题复习
- Java基础学习总结(106)——高级JAVA工程师必需技能