基本事件模型 雖然相對來說,可以比較獨立於瀏覽器運行,但不若進階事件模型那麼有彈性,例如無法註冊多個事件。進階事件模型 DOM Level 2 事件模型 與 Internet Explorer 事件模型 雖然功能較多,但各自運行於標準相容瀏覽器與Internet Explorer中,若要以進階模型來處理事件,必須處理跨瀏覽器議題。
基本概念是物件偵測,例如,在註冊click事件時,若單純處理addEventListener()與attachEvent()的差異,可以如下:
if(element.addEventListener) {
element.addEventListener('click', handler, false);
}
else if(element.attachEvent) {
element.attachEvent('onclick', handler);
}
element.addEventListener('click', handler, false);
}
else if(element.attachEvent) {
element.attachEvent('onclick', handler);
}
由於Internet Explorer不支援捕捉階段,所以必須放棄捕捉階段的跨瀏覽器處理。每次在處理事件註冊時,都必須撰寫這樣的程式碼,一來麻煩,二來每次都作物件偵測也會耗點效能。可以在載入後進行物件偵測,動態建立函式並封裝註冊行為。例如:
var bind;
function proxy(element, handler) {
return function() {
return handler.apply(element, arguments);
};
}
if(document.addEventListener) {
bind = function(element, eventType, handler) {
element.addEventListener(eventType, proxy(element, handler), false);
};
}
else if(document.attachEvent) {
bind = function(element, eventType, handler) {
element.attachEvent('on' + eventType, proxy(element, handler));
};
}
function proxy(element, handler) {
return function() {
return handler.apply(element, arguments);
};
}
if(document.addEventListener) {
bind = function(element, eventType, handler) {
element.addEventListener(eventType, proxy(element, handler), false);
};
}
else if(document.attachEvent) {
bind = function(element, eventType, handler) {
element.attachEvent('on' + eventType, proxy(element, handler));
};
}
上面的例子標準化了事件註冊的方式,而且可以統一在事件處理器中使用this參考至元素實例。綁定事件時可以如下:
bind(somBtn, 'click', function() {
var id = this.id;
...
});
var id = this.id;
...
});
接下來還要考慮統一Event實例的取得方式。如果要統一經用處理器的第一個參數傳入,可以如下:
var bind;
if(document.addEventListener) {
bind = function(element, eventType, handler) {
element.addEventListener(eventType, function(event) {
return handler.call(event.currentTarget, event);
}, false);
};
}
else if(document.attachEvent) {
bind = function(element, eventType, handler) {
element.attachEvent('on' + eventType, function() {
return handler.call(element, window.event);
});
};
}
if(document.addEventListener) {
bind = function(element, eventType, handler) {
element.addEventListener(eventType, function(event) {
return handler.call(event.currentTarget, event);
}, false);
};
}
else if(document.attachEvent) {
bind = function(element, eventType, handler) {
element.attachEvent('on' + eventType, function() {
return handler.call(element, window.event);
});
};
}
綁定事件時可以如下,統一由第一個參數取得Event實例:
bind(somBtn, 'click', function(event) {
var id = this.id;
...
});
var id = this.id;
...
});
如果想統一讓事件處理器傳回false停止預設行為,可以再進一步修改如下:
var bind;
if(document.addEventListener) {
bind = function(element, eventType, handler) {
element.addEventListener(eventType, function(event) {
var result = handler.call(event.currentTarget, event);
if(result === false) {
event.preventDefault();
}
return result;
}, false);
};
}
else if(document.attachEvent) {
bind = function(element, eventType, handler) {
element.attachEvent('on' + eventType, function() {
var result = handler.call(element, window.event);
if(result === false) {
window.event.returnValue = false;
}
return result;
});
};
}
if(document.addEventListener) {
bind = function(element, eventType, handler) {
element.addEventListener(eventType, function(event) {
var result = handler.call(event.currentTarget, event);
if(result === false) {
event.preventDefault();
}
return result;
}, false);
};
}
else if(document.attachEvent) {
bind = function(element, eventType, handler) {
element.attachEvent('on' + eventType, function() {
var result = handler.call(element, window.event);
if(result === false) {
window.event.returnValue = false;
}
return result;
});
};
}
Event實例上的一些特性存在著跨瀏覽器差異性,例如target與srcElement,stopPropagation()與cancelBubble特性等,可以建立一個物件來統一Event實例上的特性。例如:
var bind;
if(document.addEventListener) {
bind = function(element, eventType, handler) {
element.addEventListener(eventType, function(event) {
var evn = {
// 複製特性
target : event.target,
currentTarget : event.currentTarget,
...
// 封裝方法
stopPropagation : function() {
event.stopPropagation();
},
...
};
var result = handler.call(event.currentTarget, evn);
if(!result) {
event.preventDefault();
}
return result;
}, false);
};
}
else if(document.attachEvent) {
bind = function(element, eventType, handler) {
element.attachEvent('on' + eventType, function() {
var evn = {
// 複製特性
target : window.event.srcElement,
currentTarget : element,
...
// 封裝方法
stopPropagation : function() {
window.event.cancelBubble = true;
},
...
};
var result = handler.call(element, evn);
if(result === false) {
window.event.returnValue = false;
}
return result;
});
};
}
if(document.addEventListener) {
bind = function(element, eventType, handler) {
element.addEventListener(eventType, function(event) {
var evn = {
// 複製特性
target : event.target,
currentTarget : event.currentTarget,
...
// 封裝方法
stopPropagation : function() {
event.stopPropagation();
},
...
};
var result = handler.call(event.currentTarget, evn);
if(!result) {
event.preventDefault();
}
return result;
}, false);
};
}
else if(document.attachEvent) {
bind = function(element, eventType, handler) {
element.attachEvent('on' + eventType, function() {
var evn = {
// 複製特性
target : window.event.srcElement,
currentTarget : element,
...
// 封裝方法
stopPropagation : function() {
window.event.cancelBubble = true;
},
...
};
var result = handler.call(element, evn);
if(result === false) {
window.event.returnValue = false;
}
return result;
});
};
}
以上示範的是跨瀏覽器處理事件的概念,其它解除事件綁定等方法也可以作類似考量。關於跨瀏覽器事件處理,你還可以參考 原味 JavaScript 的跨瀏覽器事件處理。