Native DOM Event Module

我是在看tap.js的程式碼時,才真的注意到 DOM 的 event module (事件模組)其實我們已經可以拿來用了,關鍵在於比較少人注意到的dispatchEvent這個 method,這個 method 可以讓你對任意的 DOM Node 觸發任意事件,不管是 click 事件、mousemove 事件,還是 keydown 事件都可以,所以理論上你可以用 JS 模擬所有使用者做的操作,要做 integration 測試也沒問題,不過像是滑鼠拖拉要模擬就有點辛苦了。而除了原生有的事件外,其實 dispatchEvent 還支援自訂事件,對於自訂事件用途還不清楚的可以先看看以前的文章

dispatchEvent 的用法和一般 JavaScript Library 的 trigger event 不太一樣,要先建立一個 event 物件給他:

var event = document.createEvent('Event');
event.initEvent(type, true, true);

domnode.dispatchEvent(event);

其中的 type 才是你想要觸發的事件名稱,而第一個 createEvent 收的參數,除了 "Event" 這個最通用的之外,還有很多種原生事件,如果是要自訂事件的話,可以用最通用的 "Event"。不過其實,還有一個比較新的 API:

var event = new CustomEvent(type, {bubbles: true, cancelable: true});

這是 DOM Level 4 裡面提出來的Custom Event Constructor,在這之前的 Level 3 就可以用第一個範例的方法產生 custom event,要先 create 再 init。這邊岔題一下,應該有人會注意到,createEvent 後 initEvent 那邊的 API 設計和 CustomEvent 不一樣,initEvent 收三個值,後面兩個都傳了 true,可是這樣的 API 設計近年來都被認為是不好的,因為你看程式碼,你完全不知道這兩個 true 是什麼意思,所以都是建議用第二種 API 的設計,收一個 options 物件,其實,initEvent 那兩個 true 分別就是 CustomEvent 後面的 bubbles 和 cancelable,但是後者的設計就可以讓程式碼看一眼就知道那兩個 true 是什麼用途的,看到這種不止開發者的程式在進化,連標準也跟著一起演進的現象還蠻有趣的~

Custom Event Constructor 目前 IE 的支援還比較差,所以還不能直接使用,還是要做一下 feature detection。至於哪些物件可以使用這些 method,除了 DOM Node,外,其實一般瀏覽器都還有一些物件可以聽事件,像是 window、document、 XMLHttpRequest instance 等,很可惜不是任意物件都可以,事實上,這些可以使用 DOM Event Module 的物件都是實作了EventTarget這個介面,不過又要再說一次很可惜的,目前沒辦法把瀏覽器內的這些實作偷出來給其它物件使用,幸好 Andrea Giammarchi 有做了一個放在 Github 上,可以給一般物件繼承去用。

我曾經寫過一篇文章介紹addEventListener 的第三個參數,這篇文章的最後我要來介紹第二個參數,第二個參數一般都是丟 function 進去,不過他其實也可以丟EventListener進去,基本上就是一個物件有 handleEvent 來當事件的 callback 而已,不過像 tap.js 就有很有趣的用法,它不管是什麼事件,在加上 addEventListener 時丟進去的都是 this 物件,實際上則是統一在 handleEvent 裡面在根據事件的不同去作不同的事情

Tap.prototype.handleEvent = function (e) {
    switch (e.type) {
        case 'touchstart': this.start(e); break;
        case 'touchmove': this.move(e); break;
        case 'touchend': this.end(e); break;
        case 'touchcancel': this.cancel(e); break;
        case 'mousedown': this.start(e); break;
        case 'mousemove': this.move(e); break;
        case 'mouseup': this.end(e); break;
    }
};
comments powered by Disqus