前言
在平常開發過程中,就算不使用現在主流的框架也至少得使用個Jquery,這些工具幫我們統一不同瀏覽器平臺之間的差異和細節,可以將注意力集中到開發上來.
不過有意思的一點是,在看完高程的N年后我居然連event對象中的target和currentTarget屬性的區別都忘記了.
先提幾個引子:
你能說出event.currentTarget和event.target的區別嗎?
如果可以那么event.srcElement和事件監聽函數中的this呢
如何使用編程的方式來觸發事件,而不借助瀏覽器默認觸發方式?
如何創建一個我們自己的Event對象,然后自定義我們的事件?
實現上方的內容的同時該如何兼容IE瀏覽器?
如果這幾個內容你都熟悉了,那么這篇文章不會給你帶來太多的幫助.
在正文開始之前先來瀏覽一個表格,來看一下不同瀏覽器之間Event對象的屬性有何不同:
var button = document.getElementById("button"); button.addEventListener("click",function (event) { console.log(event); });
在下方的表格中我們記錄了不同瀏覽器之間click點擊后event可用的屬性列表(刪除了控制臺輸出的原型和函數引用):
firefox67 | chrome72 | edge44.17763.1.0 | ie11 | ie9 |
---|---|---|---|---|
altKey | altKey | altKey | altKey | altKey |
bubbles | bubbles | bubbles | AT_TARGET | AT_TARGET |
button | button | button | bubbles | bubbles |
buttons | buttons | buttons | BUBBLING_PHASE | BUBBLING_PHASE |
cancelBubble | cancelBubble | cancelable | button | button |
cancelable | cancelable | cancelBubble | buttons | buttons |
clientX | clientX | clientX | cancelable | cancelable |
clientY | clientY | clientY | cancelBubble | cancelBubble |
composed | composed | ctrlKey | CAPTURING_PHASE | CAPTURING_PHASE |
ctrlKey | ctrlKey | currentTarget | clientX | clientX |
currentTarget | currentTarget | defaultPrevented | clientY | clientY |
defaultPrevented | defaultPrevented | detail | constructor | constructor |
detail | detail | eventPhase | ctrlKey | ctrlKey |
eventPhase | eventPhase | fromElement | currentTarget | currentTarget |
explicitOriginalTarget | fromElement | height | defaultPrevented | defaultPrevented |
isTrusted | isTrusted | isPrimary | detail | detail |
layerX | layerX | isTrusted | deviceSessionId | eventPhase |
layerY | layerY | layerX | eventPhase | fromElement |
metaKey | metaKey | layerY | fromElement | isTrusted |
movementX | movementX | metaKey | height | layerX |
movementY | movementY | movementX | hwTimestamp | layerY |
mozInputSource | offsetX | movementY | isPrimary | metaKey |
mozPressure | offsetY | offsetX | isTrusted | offsetX |
offsetX | pageX | offsetY | layerX | offsetY |
offsetY | pageY | pageX | layerY | pageX |
originalTarget | path | pageY | metaKey | pageY |
pageX | relatedTarget | pointerId | offsetX | relatedTarget |
pageY | returnValue | pointerType | offsetY | screenX |
rangeOffset | screenX | pressure | pageX | screenY |
rangeParent | screenY | relatedTarget | pageY | shiftKey |
region | shiftKey | returnValue | pointerId | srcElement |
relatedTarget | sourceCapabilities | screenX | pointerType | target |
returnValue | srcElement | screenY | pressure | timeStamp |
screenX | target | shiftKey | relatedTarget | toElement |
screenY | timeStamp | srcElement | rotation | type |
shiftKey | toElement | target | screenX | view |
srcElement | type | tiltX | screenY | which |
target | view | tiltY | shiftKey | x |
timeStamp | which | timeStamp | srcElement | y |
type | x | toElement | target | |
view | y | twist | tiltX | |
which | type | tiltY | ||
x | view | timeStamp | ||
y | which | toElement | ||
width | type | |||
x | view | |||
y | which | |||
width | ||||
x | ||||
y |
通過這個表格我們可以觀察Event對象在不同瀏覽器之間結構是不同的,出人意料的是即使是在現代瀏覽器中事件對象也存在著差異.
當然這篇文章可不是將所有的Event屬性都將一遍,要知道不同的事件的Event對象結構是不同的.
吐槽:本來是打算提供IE8的但是ie8不能使用addEventListener來監聽事件懶得去搞ie那套數據了.
currentTarget,target,srcElement,this currentTarget一句話:
哪個元素上監聽的事件,event.currentTarget返回的就是這個對象的本身的引用.
如果你的一個事件監聽函數被注冊到了多個DOM元素上,利用這個屬性你就可以判斷是誰觸發的事件.
this回調函數中的this === event.currentTarget.
button.addEventListener("click",function (event) { console.log(event.currentTarget === this); // true });target
event.target和上面的三者不同,這里面涉及到了DOM中的一個基本知識事件冒泡和事件攔截.
關于這兩點我相信大家都已經了解了,即使不了解網上介紹的文章也有一大堆.
我們用事件冒泡來舉例,并且改寫我們之前的那個例子:
var wrap = document.getElementById("wrap"), button = document.getElementById("button"); // 注意我們監聽的是wrap的click事件,而不是button的click事件 wrap.addEventListener("click",function (event) { // event.target指向的是按鈕,因為我們點擊的是按鈕 console.log(event.target === button && event.target === event.srcElement); // true // 當我們點擊按鈕觸發的事件冒泡到了wrap,所以觸發了wrap的click事件, // 此時currentTarget指向的是wrap console.log(wrap===this && wrap === event.currentTarget); // true // 直接打印event然后控制臺中查看currentTaget會返回null // 你可以將他賦值到一個變量在打印輸出這個變量 // see https://github.com/vuejs/vue/issues/6867#issuecomment-338195468 })
在這個例子中,我們點擊頁面中的按鈕,然后再按鈕的包裹div中接收到了button冒泡上來的事件,這其中:
this 和 currentTarget指向的都是添加了監聽器的對象這里就是wrap
target 和 srcElement指向的是觸發了事件的元素
事件委托也是event.target最常見的用途之一:
// Make a list var ul = document.createElement("ul"); document.body.appendChild(ul); var li1 = document.createElement("li"); var li2 = document.createElement("li"); ul.appendChild(li1); ul.appendChild(li2); function hide(e){ // e.target 引用著
https://developer.mozilla.org...srcElement
簡單理解event.srcElement === event.target.
Event.srcElement 是標準的 Event.target 屬性的一個別名。它只對老版本的IE瀏覽器有效。https://developer.mozilla.org...
參考之前的表格后看來這個屬性還沒有被干掉,在目前最新的瀏覽器上它依然存在,不過已經不建議使用,除非你需要向下兼容.
完整的事件編程 EventTarget接口當我們在使用如下的方法的時候:
elem.addEventListener
elem.removeEventListener
elem.dispatchEvent
實際上是在使用EventTarget接口上的功能.
例如我們可以創建一個新的EventTarget對象來添加事件監聽:
const a = new EventTarget; a.addEventListener("click",()=>{ })
但是這沒有任何意義,因為這里沒有事件被觸發.我們僅僅是添加了事件監聽器而已.
為了達到我們目的通過編程的方式來執行完整的事件流程我們還需要完成如下的幾步:
繼承EventTarget而不是直接使用EventTarget的實例,
在事件監聽函數中傳遞Event對象
找個地方來觸發這個事件
首先我們來繼承EventTarget對象:
繼承EventTarget在瀏覽器中大部分可以添加刪除事件的對象都繼承了EventTarget對象.
你可以在控制臺選擇一個HTML元素一路查找原型鏈得到.
但是他們進過重重繼承,都有自己的獨特屬性和事件類型甚至是不同的構造函數.
為了和已有的事件進行區分我們這里需要對EventTarget進行繼承:
// --- 包裝EventTarget開始 function MyEventTarget() { var target = document.createTextNode(null); this.addEventListener = target.addEventListener.bind(target); this.removeEventListener = target.removeEventListener.bind(target); this.dispatchEvent = target.dispatchEvent.bind(target); } MyEventTarget.prototype = EventTarget.prototype; // --- 包裝EventTarget結束 // --- 創建我們繼承EventTarget的構造函數 function myElem() { } myElem.prototype = new MyEventTarget; myElem.prototype.constructor = myElem; // 創建實例 const instance = new myElem(); instance.addEventListener("click",()=>{ // 現在我們實例可以監聽事件了 }); console.log(instance);
繼承的過程看似非常復雜,尤其是包裝EventTarget顯得多此一舉.但是搞定EventTarget的繼承確實花了我大量的時間去尋找解決方案.
你完全可以編寫自己的繼承方式來去繼承EventTarget,不過你會發現這其中的坑非常深.
簡單來說,EventTarget在JavaScript中真的就是一個接口,雖然是以函數的形式存在,但是它不是構造函數(這點在Chrome64 和firefox59后進行了修改).
總之通過原型鏈繼承的EventTarget統統無法工作,如果使用ES6的類式繼承在現代瀏覽器中(Chrome64和firefox59后)可以使用class來進行繼承.
詳細參考:創建我們的Event對象https://stackoverflow.com/que...
獲取一個event:
document.getElementById("button").addEventListener("click",(event)=>{ // event console.log(event); })
如果你在瀏覽器中運行這段代碼并且在控制臺中查看,你會發現變量event的名稱MouseEvent,如果你沿著原型鏈向上你會發現繼承的是UIEvent再次向上查看則是真正的Event.
事件觸發中傳遞的第一個參數我們通常叫它event,所有的event對象都基于Event,但是這不意味著這種關系的窗戶紙就只有一層,click事件中的event和Event之間就隔著一個UIEvent.
通常隨著event繼承的層數越多,event對象身上的屬性也會越來越多.
現在我們來創建一個標準的Event對象:
// 使用全局的Event new Event("test",{ // 事件類型 bubbles:false, // 是否冒泡 默認false cancelable:false,// 是否可以被取消 默認false });
https://developer.mozilla.org...
如果你在瀏覽器中觀察這個對象,你會發現事件上常見的屬性諸如:
event.target
event.currentTarget
event.preventDefault()
都在這個new Event()返回的對象中,由于其他類型的事件都繼承自Event這也解釋了為什么事件對象中總是有這些屬性.
和繼承EventTarget一樣,使用Event的過程也同樣艱難,總的來說使用Event的難點在于它有兩套API:
第一套比較新的API提供了現代的接口,也就是之前例子中的方式.
在創建一個已有的事件的時候,你只需要使用全局的構造函數就可以,
例如:new MouseEvent("test",/*對應MouseEvent的參數選項*/),
但是缺點就是不支持IE瀏覽器.
第二套API支持IE瀏覽器,但是使用過程比較繁瑣
使用Event.createEvent(/*事件類型*/)創建對應事件類型的Event對象,
使用Event.initEvent()來初始化事件,并且提供對應事件類型的參數,
如果你創建一個MouseEvent類型的事件InitEvent方法最多需要15個參數.
這種情況下使用new MouseEvent()傳入對象配置的形式就簡單多了.
一篇值得參考的文章,使用createEvent apihttps://www.cnblogs.com/ggz19...
此外不同種類的事件,都有自己的全局構造函數,不同類型的構造函數的第二個參數中的選項也是不同的.
其他的構造函數請參考這里.
觸發我們的事件觸發事件就顯得簡單多了,我們需要使用EventTarget.dispatchEvent方法.
在我們之前創建的實例上進行事件的觸發:
function MyEventTarget() { var target = document.createTextNode(null); this.addEventListener = target.addEventListener.bind(target); this.removeEventListener = target.removeEventListener.bind(target); this.dispatchEvent = target.dispatchEvent.bind(target); } MyEventTarget.prototype = EventTarget.prototype; function myElem() { } myElem.prototype = new MyEventTarget; myElem.prototype.constructor = myElem; const instance = new myElem(); instance.addEventListener("test", (event) => { console.log(event); // 監聽事件并且打印實例 }); const myEvent = new Event("test"); // 創建Event實例 instance.dispatchEvent(myEvent); // 觸發事件
當你調用dispatchEvent的時候,EventTarget會按照對應事件注冊的順序來同步執行這些事件監聽器.
如果在事件監聽器中調用了event.preventDefault,那么dispatchEvent就返回false反之返回true(前提是cancleable為true).
詳細參考編程式的事件觸發https://developer.mozilla.org...
我們在頁面中來一次具體的實戰,首先建立如下的HTML結構:
我們在#wrap中監聽click事件,然后在#button觸發click事件.
這樣我們可以練習一下Event中bubbles(允許冒泡)參數的使用,
另外還可以測試click事件中的Event對象如果不是MouseEvent的實例那么監聽器是否會被觸發.
const button = document.getElementById("button"), wrap = document.getElementById("wrap"); wrap.addEventListener("click", (event) => { console.log(event); // 打印event對象 }); const myEvent1 = new Event("click", { bubbles: false, // 不可以冒泡 }); const myEvent2 = new Event("click", { bubbles: true, // 可以冒泡 }); button.dispatchEvent(myEvent1); // 這次沒有打印出內容 button.dispatchEvent(myEvent2); // 這次打印出了內容
結論很明確:
dispatchEvent執行的時候只要是Event的實例且類型相同那么監聽器就會被觸發.
bubbles參數可以控制該事件是否允許冒泡
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/54771.html
前言 在平常開發過程中,就算不使用現在主流的框架也至少得使用個Jquery,這些工具幫我們統一不同瀏覽器平臺之間的差異和細節,可以將注意力集中到開發上來. 不過有意思的一點是,在看完高程的N年后我居然連event對象中的target和currentTarget屬性的區別都忘記了. 先提幾個引子: 你能說出event.currentTarget和event.target的區別嗎? 如果可以那么ev...
摘要:回調函數,一般在同步情境下是最后執行的,而在異步情境下有可能不執行,因為事件沒有被觸發或者條件不滿足。同步方式請求異步同步請求當請求開始發送時,瀏覽器事件線程通知主線程,讓線程發送數據請求,主線程收到 一直以來都知道JavaScript是一門單線程語言,在筆試過程中不斷的遇到一些輸出結果的問題,考量的是對異步編程掌握情況。一般被問到異步的時候腦子里第一反應就是Ajax,setTimse...
閱讀 1271·2021-11-15 18:14
閱讀 3128·2021-08-25 09:38
閱讀 2663·2019-08-30 10:55
閱讀 2673·2019-08-29 16:39
閱讀 1305·2019-08-29 15:07
閱讀 2446·2019-08-29 14:14
閱讀 810·2019-08-29 12:36
閱讀 909·2019-08-29 11:21