在上一篇Blog 《舍去jQuery,面向M編程,而不是面向V編程》中,在面向?qū)ο缶幊痰睦又行枰壎ㄊ录綄?duì)象的方法上,而不是綁定到全局方法上。在這一篇里,我們可以繼續(xù)延續(xù)這個(gè)話題,以完善事件綁定機(jī)制。 我們想要達(dá)到的目標(biāo)是,一個(gè)對(duì)象已經(jīng)封裝了事件處理的邏輯,而我們知道對(duì)象本身是不能捕獲事件的,只有DOM元素可以捕獲事件,如何動(dòng)態(tài)地綁定DOM事件的處理句柄到對(duì)象實(shí)例上。看下面的代碼:
var Button = function(id){
this.div = document.createElement("div");
this.div.id = id;
this.onClick = function(){
alert(this.div.id); // 這里的this是div,而不是我們想要的Button實(shí)例
};
this.div.onclick = this.onClick;
}; |
代碼的意圖很明顯,div上的onclick由Button對(duì)象的onClick方法來(lái)處理,但我們會(huì)發(fā)現(xiàn),其實(shí)這樣工作是有問(wèn)題的,this.onClick方法里的this其實(shí)在事件處理階段是指向div的,而不是我們想當(dāng)然的Button實(shí)例。如何讓this指向Button實(shí)例呢?我們提到一個(gè)技巧, 擴(kuò)展JavaScript的Function原型:
Function.prototype.bind = function(context){
var _method = this;
return function(e) {
var array = [e || window.event];
return _method.apply(context, array);
};
}; |
為一個(gè)方法綁定執(zhí)行的上下文,這樣就為將this指定到Button實(shí)例提供了可能,上面Button的例子可以改為:
Function.prototype.bind = function(context){
var _method = this;
return function(e) {
var array = [e || window.event];
return _method.apply(context, array);
};
};
var Button = function(id){
this.div = document.createElement("div");
this.div.id = id;
this.onClick = function(e){
alert(this.div.id); // 這時(shí)this是指向Button實(shí)例的
};
this.div.onclick = this.onClick.bind(this);
};
|
這樣雖然可以工作了,但還不完美,如果我們需要在處理事件時(shí)有多個(gè)處理句柄,并且還能隨時(shí)移除事件句柄,那上面的方法還需要改進(jìn),就是加入attachEvent和detachEvent了。
Function.prototype.bind = function(context){
var _method = this;
return function(e) {
var array = [e || window.event];
return _method.apply(context, array);
};
};
/**
* Attach event listener to a DOM object
*
* @param domObj
* @param evType, for example "click", "load" etc
* @param thi$, the context of handler
* @param handler, event handler
*/
$attachEvent = function(domObj, evType, thi$, handler){
var _handler = handler.bind(thi$);
if(domObj.addEventListener){
domObj.addEventListener(evType, _handler, false);
}else{
domObj.attachEvent("on"+evType, _handler);
}
return _handler;
};
/**
* Detach event listener from a DOM object
*
* @see J$VM.attachEvent(domObj, evType, thi$, handler)
*/
$detachEvent = function(domObj, evType, thi$, handler){
if(domObj.removeEventListener){
domObj.removeEventListener(evType, handler, false);
}else{
domObj.detachEvent("on"+evType, handler);
}
};
var Button = function(id){
this.div = document.createElement("div");
this.div.id = id;
this.onClick = function(e){
alert(this.div.id); // 這時(shí)this是指向Button實(shí)例的
// 假設(shè)在這個(gè)邏輯中我們需要移除事件句柄,但其實(shí)這一句也是不如我們預(yù)期的。
$detachEvent(this.div, "click", this, this.onClick);
};
$attachEvent(this.div, "click", this, this.onClick);
};
|
引入這兩個(gè)方法后,再看Button的例子,好像完美了,但實(shí)際上,detachEvent(this.div, "click", this, this.onClick)這一句并不如我們預(yù)期的那樣工作,因?yàn),在attachEvent內(nèi)部,我們attach的實(shí)際上是bind方法返回的方法,而不是this.onClick,所以detachEvent時(shí),并不能找到這個(gè)this.onClick。我們還需要繼續(xù)改造bind方法和detachEvent方法。
Function.prototype.bind = function(context){
var _method = this;
_method.__handler__ = function(e) {
// 在_method里暗藏一個(gè)真實(shí)的綁定句柄
var array = [e || window.event];
return _method.apply(context, array);
};
return _method.__handler__;
};
$detachEvent = function(domObj, evType, thi$, handler){
var _handler = handler.__handler__ || handler; // 取出__handler__
if(domObj.removeEventListener){
domObj.removeEventListener(evType, _handler, false);
}else{
domObj.detachEvent("on"+evType, _handler);
}
};
|
到這里,我們的事件綁定機(jī)制基本上完美了,最后附上一段完整的代碼:
(function(){
window.J$VM = {};
Function.prototype.bind = function(context){
var _method = this;
_method.__handler__ = function(e) {
var array = [e || window.event];
return _method.apply(context, array);
};
return _method.__handler__;
};
/**
* Attach event listener to a DOM object
*
* @param domObj
* @param evType, for example "click", "load" etc
* @param thi$, the context of handler
* @param handler, event handler
*/
J$VM.attachEvent = function(domObj, evType, thi$, handler){
var _handler = handler.bind(thisObj);
if(domObj.addEventListener){
domObj.addEventListener(evType, _handler, false);
}else{
domObj.attachEvent("on"+evType, _handler);
}
return _handler;
};
/**
* Detach event listener from a DOM object
*
* @see J$VM.attachEvent(domObj, evType, thi$, handler)
*/
J$VM.detachEvent = function(domObj, evType, thi$, handler){
var _handler = handler.__handler__ || handler;
if(domObj.removeEventListener){
domObj.removeEventListener(evType, _handler, false);
}else{
domObj.detachEvent("on"+evType, _handler);
}
};
/**
* Cancel event bubble
*/
J$VM.cancelBubble = function(e){
if(e.stopPropagation){
e.stopPropagation();
}else{
window.event.cancelBubble = true;
}
};
})(); |
|