JavaScript设计模式之桥接模式与IE下事件函数的this指向与执行顺序

在javascript 设计模式这本书中看到桥接模式的例子,先是在那个事件监听器中

addEvent(element, 'click', getBeerByIdBridge);

function getBeerByIdBridge (e) {

  var id = this.id;

  getBeerById(id, function(beer) {

    console.log('Requested Beer: '+beer);

  });

}
到这里,我迷惑了一下,因为我记得以前在看事件这块的时候,明白了this在IE中会指向window,而不是指向这个DOM元素,然后我就去研究怎样修正IE下怎样this指向DOM元素,在这里遇到了个以前碰到过的问题,这里也说明下。

在修正指向问题通常用到call与apply,element.attachEvent("obclick",func.call(element)),如果这样的话遇到的问题是一进入浏览器直接执行func函数,执行完毕后,报错类型不匹配,在这里纠结了好久,最后才发现问题在于这里添加事件相当于func(),肯定会直接执行这个函数,然后将这个函数的执行结果返回给回调函数,没有返回函数,当然会类型不匹配。

下边列出修正了IE下this的指向window,与IE8下同一个元素,绑定多个事件执行的顺序 与W3C标准恰好相反,但是在IE6与IE7下,事件执行的顺序随机,毫无规律可循,这里只是解决了IE8下的问题。

        function IEbind(ele, sType, fn) { //
		 //为了结构清析,把IE里的绑定处理独立成一个方法
		 if (!ele['attr' + sType]) {
			 ele['attr' + sType] = [];
		}
		var fnTemp = (function(fn, ele) {
			//使原来方法绑定之后,this关键字能够指向ele
			//相当于穿了马甲的方法
			return function(event) {
				fn.call(ele, event)
			}
		})(fn, ele);
		fnTemp.photo = fn;//使原来的方法和现在穿了马甲的方法发生关联
		ele['attr' + sType].push(fnTemp);
		ele.attachEvent('on' + sType, fnTemp);
	}
	function bind(ele, sType, fn) {
		if (ele.addEventListener) {
			ele.addEventListener(sType, fn, false);
		} else if (ele.attachEvent) {
			if (!ele['sequence' + sType]) {//这个数组是负责按顺序存诸,就是按绑定的顺序把方法存在这个数组里,
				ele['sequence' + sType] = [];//如果它不存在,就自定义一个这样的数组
			}
			ele['sequence' + sType].push(fn);
			//W3C先绑定的先运行
			//先绑定的后运行 (倒序)
			//没有事件被绑定的情况下(ele['attr'+sType]不存在或它的length为0的时候)不需把顺序重新组织
			//没有方法被绑定
			if ((!ele['attr' + sType]) || ele['attr' + sType].length == 0)
				IEbind(ele, sType, fn);
			else {
				while (ele['attr' + sType].length) {
					//把原来绑定的都移除,然后再把顺序颠倒过来重新绑定
					unbind(ele, sType, ele['attr' + sType][0].photo);
				}
				for ( var i = ele['sequence' + sType].length - 1; i >= 0; i--) {
					//然后再把顺序颠倒过来重新绑定
					IEbind(ele, sType, ele['sequence' + sType][i]);
				}
			}
			//被绑定元素上的函数里的this关键字都会指向window	
		} else {
			//ele['on'+sType]=fn;
			//不需要考虑
		}
	}
	function unbind(ele, sType, fn) {
		if (ele.removeEventListener) {
			ele.removeEventListener(sType, fn, false);
		} else if (ele.detachEvent) {
			if (ele['attr' + sType]) {
				for ( var i = 0; i < ele['attr' + sType].length;) {
					var fnTemp = ele['attr' + sType][i]
					if (fnTemp.photo == fn) {
						ele.detachEvent('on' + sType, fnTemp);
						ele['attr' + sType].splice(i, 1);
						//如果删掉了,则不需要i++
					} else {
						i++;//删不掉,再i++
					}
				}
			}
		}
	}

这个例子也是摘自网上的写法,在修正IE的this指向,主要是把包装了函数fnTemp,并存储在ele['attr'+sType],在关于执行顺序的问题,把绑定的函数,绑定在ele['sequence']中,并按照倒序依次取出,再次绑定。

到这里,基本知识也够了,再来看桥接模式。

桥接模式运用场所之一:事件监听器

   先看不用桥接模式的方式

addEvent(element,'click',getBeerById);
function getBeerById(e){
       var id =this.id;
       asyncRequest('GET','beer.url?id='+ id,function(resp){
          //Callback response
          console.log('Requested Beer: '+ resp.responseText);
       });
}
这个方法根据事件监听器回调函数的工作机制,事件对象被作为参数传递给这个函数。本例中并没有使用这个参数,而只是从this对象获取ID。这里直接从this获取ID,然后接着就进行下面的逻辑,导致耦合过于紧密,还有测试起来也是麻烦事。

使用桥接模式的方式

function getBeerById(id,callback){
      asyncRequest('GET','beer.url?id='+ id,function(resp){
         callback(resp.responseText)
     });
 }
addEvent(element,'click',getBeerByIdBridge);
function getBeerByIdBridge(e){
     getBeerById(this.id,function(beer){
        console.log('Requested Beer: '+ beer);
     });
} 

从逻辑上分析,把id传给getBeerById函数式合情理的,且回应结果总是通过一个毁掉函数返回。这么理解,我们现在做的是针对接口而不是实现进行编程,用桥梁模式把抽象隔离开来。
这样,明显代码模块话,各个部分代码功能明确,耦合性大大降低,将监听器方法抽取出来,成为一个单独的API函数,而且保证该API函数与节点本身没有必然的耦合,我们就可以独立的运行getBeerById这个函数。

运用场所之二: 特权函数

除了在事件回调函数与接口之间进行桥接外,桥接模式也可以用于连接公开的API代码和私用的实现代码。此外,它还可以用来把多个类连接在一起。从类的角度来看,这意味着把接口作为公开的代码编写,而把类的实现作为私用代码编

写。

如果一个公用的接口抽象了一些也许应该属于私用性的较为复杂的任务,那么可以使用桥接模式来收集某些私用性的信息。可以用一些具有特殊权利的方法作为,桥梁以便方位私用变量空间,而不必冒险下到具体实现的浑水中。这个特例

中的桥接性函数也称之为特权函数。

下面举一个简单的例子,通过实例设置并获取其私有的属性:

var Public= function(){
    //这里定义secret 总数为3
    var secret= 2;
    //通过这个方法,即可获取secret 
    this.getSecret = function(){
        return recret;
    };
};

var public= new Public();
alert(public.getSecret()); //这里就能得到secret  这个私有变量

桥接模式,就是把给抽象与现实对象搭一座桥,让对象方法即联系在一起,又是独立变化的,让代码耦合性降低的一种设计模式。

下面是JAVASCRIPT 设计模式书上的一个比较大型的例子

//给对象添加原型方法,并返回该对象
Function.prototype.method=function(name,fn){
	this.prototype[name]=fn;
	return this;
};
//遍历方法
if(!Array.prototype.forEach){
	Array.method('forEach',function(fn,thisObj){
		var scope=thisoBJ||window;
		for(var i=0,len=this.length;i<len;++i){
			fn.call(scrope,this[i],i,this);
		}
	});
}
/**
 * @param 过滤的方法
 * @param 过滤方法的this指向
 * 返回这个数组符合过滤方法的值
 * */
if(!Array.prototype.filter){
	Array.method('filter',function(fn,thisObj){
		var scope=thisoBJ||window,
		    a=[];
		for(var i=0,len=this.length;i<len;++i){
			if(!fn.call(scrope,this[i],i,this)){
				continue;
			}
			a.push(this[i]);
		}
		return a;
	});
}


var asycRequest=(function(){
	function handleReadyState(o,callback){
		var poll=window.setInterval(function(){
			if(o&&o.readyState==4){
				window.clearInterval(poll);
				if(callback){
					callback(o);
				}
			}
		},50);
	}
	var getXHR=function(){
		var http;
		 //http储存该对象,以后访问直接读取
		try {
			http=new XMLHttpRequest;
			getXHR=function(){
				return new XMLHttpRequest;
			};
		} catch (e) {
			var msxml=['MSXML2.XMLHTTP.3.0',
			           'MSXML2.XMLHTTP',
			           'Microsoft.XMLHTTP'];
			for(var i=0,len=msxml.length;i<len;i++){
				try {
					http=new ActiveXObject(msxml[i]);
					getXHR=function(){
						return new ActiveXObject(msxml[i]);
					};
					break;
				} catch (e) {}
			}
		}
		return http;
	};
	return function(method,uri,callback,postData){
		var http=getXHR();
		http.open(method,uri,true);
		handleReadyState(http,callback);
		http.send(postData||null);
		return http;
	};
})();



window.DED=window.DED||{};
DED.util=DED.util||{};
DED.util.Observer=function(){
	this.fns=[];
};
DED.util.Observer.prototype={
	//发布事件	
	subscribe:function(fn){
	    this.fns.push(fn);	
	},
	//删除事件
	unsubscribe:function(fn){
		this.fns=this.fns.filter(function(el){
			if(el!==fn){
				return el;
			}
		});
	},
	//触发事件
	fire:function(o){
		this.fns.forEach(function(el){
			el(o);
		});
	}
};

DED.Queue=function(){
	this.queue=[];
	this.onComplete=new DED.util.Observer;
	this.onFailure=new DED.util.Observer;
	this.onFlush=new DED.util.Observer;
	this.retryCount=3;  //允许重新连接的次数
	this.currentRetry=0;  //当前连接的次数
	this.paused=false;   
	this.timeout=5000;   //连接未通的时间,重新连接
	this.conn={};
	this.timer={};
};
DED.Queue.method('flush',function(){
	if(!this.queue.length>0){
		return ;
	}
	if(this.paused){
		this.paused=false;
		return ;
	}
	var that=this;
	this.currentRetry++;
	var abort=function(){
		this.conn.abort();  //停止该连接
		if(that.currentRetry==that.retryCount){
			that.onFailure.fire();
			that.currentRetry=0;
		}else{
			that.flush();
		}
	};
	this.timer=window.setTimeout(abort,this.timeout);
	var callback=function(o){
		window.clearTimeout(that.timer);
		that.currentRetry=0;
		that.queue.shift();
		that.onFlush.fire(o.responseText);
		if(that.queue.length==0){
			that.onComplete.fire();
			return ;
		}
		that.flush();
	};
	this.conn=asycRequest(this.queue[0]['method'],
			              this.queue[0]['uri'],
			              callback,
			              this.queue[0]['params']);
}).method('setRetryCount',function(count){
	this.retryCount=count;
}).method('setTimeout',function(time){
	this.timeout=time;
}).method('add',function(o){
	this.queue.push(o);
}).method('pause',function(){
	this.paused=true;
}).method('dequeue',function(){
	this.queue.pop();
}).method('clear',function(){
	this.queue=[];
});
在这个JS文件中,首先扩展三种原型对象的方法,一是给对象添加原型方法,并方便以后链式调用,第二,是给数组添加遍历方法,方便操作数组,这里额外提下,其实这里,也是个桥接模式。第三,是数组的过滤方法,返回符合条件的数组。

然后申明一个观察者模式,和一个DED.queue对象,具体代码解释就是实现一个连接队列,能存储发请求的队列,并触发刷新队列请求成功、队列请求完成,请求失败的事件。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Ajax Connection Queue</title>
<script src="js/utils.js"></script>
<script src="js/queue.js"></script>
<script type="text/javascript">
 	addEvent(
			window,
			'load',
			function() {
				// 实现.
				var q = new DED.Queue;
				q.setRetryCount(5);
				q.setTimeout(3000);
				var items = $('items');
				var results = $('results');
				var queue = $('queue-items');
				// 在客户端保存跟踪自己的请求
				var requests = [];
				// 每个请求flush以后,订阅特殊的处理步骤
				q.onFlush.subscribe(function(data) {
					results.innerHTML += data;
					requests.shift();
					queue.innerHTML = requests.toString();
				});
				// 订阅时间处理步骤
				q.onFailure
						.subscribe(function() {
							results.innerHTML += ' <span style="color:red;">Connection Error!</span>';
						});
				// 订阅全部成功的处理步骤x
				q.onComplete
						.subscribe(function() {
							results.innerHTML += ' <span style="color:green;">Completed!</span>';
						});
				var actionDispatcher = function(element) {
					switch (element) {
					case 'flush':
						q.flush();
						break;
					case 'dequeue':
						q.dequeue();
						requests.pop();
						queue.innerHTML = requests.toString();
						break;
					case 'pause':
						q.pause();
						break;
					case 'clear':
						q.clear();
						requests = [];
						queue.innerHTML = '';
						break;
					}
				};
				var addRequest = function(request) {
					var data = request.split('-')[1];
					q.add({
						method : 'GET',
						uri : 'new/index?ajax=true&s=' + data,
						params : null
					});
					requests.push(data);
					queue.innerHTML = requests.toString();
				};
				addEvent(items, 'click', function(e) {
					var e = e || window.event;
					var src = e.target || e.srcElement;
					try {
						e.preventDefault();
					} catch (ex) {
						e.returnValue = false;
					}
					actionDispatcher(src.id);
				});
				var adders = $('adders');
				addEvent(adders, 'click', function(e) {
					var e = e || window.event;
					var src = e.target || e.srcElement;
					try {
						e.preventDefault();
					} catch (ex) {
						e.returnValue = false;
					}
					addRequest(src.id);
				});
			}); 
</script>
<style type="text/css" media="screen">
body {
	font: 100% georgia, times, serif;
}

h1,h2 {
	font-weight: normal;
}

#queue-items {
	height: 1.5em;
}

#add-stuff {
	padding: .5em;
	background: #ddd;
	border: 1px solid #bbb;
}

#results-area {
	padding: .5em;
	border: 1px solid #bbb;
}
</style>
</head>
<body id="example">
	<div id="doc">
		<h1>异步联接请求</h1>
		<div id="queue-items"></div>
		<div id="add-stuff">
			<h2>向队列里添加新请求</h2>
			<ul id="adders">
				<li><a href="#" id="action-01">添加 "01" 到队列</a>
				</li>
				<li><a href="#" id="action-02">添加 "02" 到队列</a>
				</li>
				<li><a href="#" id="action-03">添加 "03" 到队列</a>
				</li>
			</ul>
		</div>
		<h2>队列控制</h2>
		<ul id='items'>
			<li><a href="#" id="flush">Flush</a>
			</li>
			<li><a href="#" id="dequeue">出列Dequeue</a>
			</li>
			<li><a href="#" id="pause">暂停Pause</a>
			</li>
			<li><a href="#" id="clear">清空Clear</a>
			</li>
		</ul>
		<div id="results-area">
			<h2>结果:</h2>
			<div id="results"></div>
		</div>
	</div>
</body>
</html>
然后在这里面,事件的回调函数,与队列毫无关系,通过桥接函数actionDispatcher与addRequest,传送参数,控制队列的操作。

桥接模式就是,用书本上的就是“将抽象与现实实现隔离开来,以便二者独立变化”,来促进代码模块化,促成更简洁的实现并提高抽象的灵活性。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值