前言
- 一般寫addEventListener,我們的listener都會收到一個事件對象,而這個事件對象的類型TS卻認爲是Event類型,而其target判斷爲EventTarget類型,給的類型比較大,往往都需要我們進行斷言,爲什麼不好判斷?難道真的有意料之外的結果?
- 特別是一些我們感覺很確定的東西,比如MouseEvent,是click後的事件,不管屏幕上怎麼點擊,點的怎麼看都是個HTMLElement,難道還會有別的東西?
Event EventTarget Node Document HTMLElement 區別
- HTMLElement extends Element
- Element extends Node , Document extends Node
- Node extends EventTarget
- Event是發生在dom的事件,其中target屬性爲 EventTarget | null
- Node有很多dom的API,Document 與Element又進行擴充,像className就是Element特有的,document有cookie,domain之類特有的。
- 而HTMLElement就是擴充自Element,比Element多了offsetHeight之類屬性。
自定義事件
- 瞭解了上面關係,再說下主角,就是自定義事件,就是這個特性的存在,導致不好判斷這個事件對象到底是個啥。
- 我們以MouseEvent舉例,通常情況下,
document.addEventListener('click',listener)
這個語句中,listener就會接收到Event類型的參數,這時e.target
的類型是EventTarget | null
類型能否是HTMLElement?
- 答案肯定是不行,我們可以使用自定義事件傳個負值過去,就像這樣:
document.dispatchEvent(new MouseEvent('click',{clientX:-1,clientY:-1}))
document.dispatchEvent(new MouseEvent('click',{target:'123'}))
- 這樣找不到target,target就會變成document,而根據第一節,document是Document類型,比HTMLElement類型要大,所以不行。
類型能否是Node?
- 根據第一節可以發現,這個EventTarget有很多屬性沒有,是個比Node還大的玩意,那麼可能產生不是Node但是卻屬於EventTarget的東西嗎?這還真有,MDN舉出了3種特殊例子, XMLHttpRequest,AudioNode,AudioContext。
XMLHttpRequest extends XMLHttpRequestEventTarget extends EventTarget
可以看見,這個XMLHttpRequest既不屬於Node,但是卻屬於EventTarget,滿足了要求。另外2個不舉例了。
- 雖然滿足了要求,但一個click事件怎麼會給你傳一個XMLHttpRequest的???這怎麼看也不對啊,就算你點的再偏,頂多給你傳個document,怎麼會傳XMLHttpRequest的?
- 可以看一下這個騷操作:
let a=new XMLHttpRequest
a.addEventListener('click',(e)=>console.log(e))
let event = new Event('click')
a.dispatchEvent(event)
- 詭異的XMLHttpRequest居然可以用addEventListener,最後e的打印結果:
bubbles: false
cancelBubble: false
cancelable: false
composed: false
currentTarget: XMLHttpRequest {onreadystatechange: null, readyState: 0, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}
defaultPrevented: false
eventPhase: 0
isTrusted: false
path: []
returnValue: true
srcElement: XMLHttpRequest {onreadystatechange: null, readyState: 0, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}
target: XMLHttpRequest {onreadystatechange: null, readyState: 0, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}
timeStamp: 4900758.515000053
type: "click"
- 這就使得target並不是Node類型,而是更大的EventTarget類型。
- 有人就會說,那TS咋不能直接看調用者來進行更細的判斷,如果調用者是Node,那麼它的listener的target就應該是Node類型,如果調用者是XMLHttpRequest,那麼它的 listener的target就應該是XMLHttpRequest類型。
- 沒錯,TS的特性其實是可以辦到的,但是目前情況就是所有的類型全部都由EventTarget擴展,而target屬性全部用的都是Event上的Target屬性。
- 最好的解決方法就是對EventTarget添加泛型支持,這樣只要傳入相應的泛型,就能直接獲得想要的對象,這個問題存在了近5年還沒有解決,並且問題仍然在開放中,根據作者描述,他們的聲明文件是通過軟件自動生成的,這個源文件不能放出來給你看,另外,如果要添加泛型,會導致過不了性能測試。