跨瀏覽器事件處理



基本事件模型 雖然相對來說,可以比較獨立於瀏覽器運行,但不若進階事件模型那麼有彈性,例如無法註冊多個事件。進階事件模型 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);
}

由於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));
    };
}

上面的例子標準化了事件註冊的方式,而且可以統一在事件處理器中使用this參考至元素實例。綁定事件時可以如下:
bind(somBtn, 'click', function() {
    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);
        });
    };
}

綁定事件時可以如下,統一由第一個參數取得Event實例:
bind(somBtn, 'click', function(event) {
    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;
        });
    };
}

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;
        });
    };
}

以上示範的是跨瀏覽器處理事件的概念,其它解除事件綁定等方法也可以作類似考量。關於跨瀏覽器事件處理,你還可以參考 原味 JavaScript 的跨瀏覽器事件處理