前言
在平常開發(fā)過程中,就算不使用現(xiàn)在主流的框架也至少得使用個(gè)Jquery,這些工具幫我們統(tǒng)一不同瀏覽器平臺(tái)之間的差異和細(xì)節(jié),可以將注意力集中到開發(fā)上來.
不過有意思的一點(diǎn)是,在看完高程的N年后我居然連event對(duì)象中的target和currentTarget屬性的區(qū)別都忘記了.
先提幾個(gè)引子:
你能說出event.currentTarget和event.target的區(qū)別嗎?
如果可以那么event.srcElement和事件監(jiān)聽函數(shù)中的this呢
如何使用編程的方式來觸發(fā)事件,而不借助瀏覽器默認(rèn)觸發(fā)方式?
如何創(chuàng)建一個(gè)我們自己的Event對(duì)象,然后自定義我們的事件?
實(shí)現(xiàn)上方的內(nèi)容的同時(shí)該如何兼容IE瀏覽器?
如果這幾個(gè)內(nèi)容你都熟悉了,那么這篇文章不會(huì)給你帶來太多的幫助.
在正文開始之前先來瀏覽一個(gè)表格,來看一下不同瀏覽器之間Event對(duì)象的屬性有何不同:
var button = document.getElementById("button"); button.addEventListener("click",function (event) { console.log(event); });
在下方的表格中我們記錄了不同瀏覽器之間click點(diǎn)擊后event可用的屬性列表(刪除了控制臺(tái)輸出的原型和函數(shù)引用):
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 |
通過這個(gè)表格我們可以觀察Event對(duì)象在不同瀏覽器之間結(jié)構(gòu)是不同的,出人意料的是即使是在現(xiàn)代瀏覽器中事件對(duì)象也存在著差異.
當(dāng)然這篇文章可不是將所有的Event屬性都將一遍,要知道不同的事件的Event對(duì)象結(jié)構(gòu)是不同的.
吐槽:本來是打算提供IE8的但是ie8不能使用addEventListener來監(jiān)聽事件懶得去搞ie那套數(shù)據(jù)了.
currentTarget,target,srcElement,this currentTarget一句話:
哪個(gè)元素上監(jiān)聽的事件,event.currentTarget返回的就是這個(gè)對(duì)象的本身的引用.
如果你的一個(gè)事件監(jiān)聽函數(shù)被注冊(cè)到了多個(gè)DOM元素上,利用這個(gè)屬性你就可以判斷是誰觸發(fā)的事件.
this回調(diào)函數(shù)中的this === event.currentTarget.
button.addEventListener("click",function (event) { console.log(event.currentTarget === this); // true });target
event.target和上面的三者不同,這里面涉及到了DOM中的一個(gè)基本知識(shí)事件冒泡和事件攔截.
關(guān)于這兩點(diǎn)我相信大家都已經(jīng)了解了,即使不了解網(wǎng)上介紹的文章也有一大堆.
我們用事件冒泡來舉例,并且改寫我們之前的那個(gè)例子:
var wrap = document.getElementById("wrap"), button = document.getElementById("button"); // 注意我們監(jiān)聽的是wrap的click事件,而不是button的click事件 wrap.addEventListener("click",function (event) { // event.target指向的是按鈕,因?yàn)槲覀凕c(diǎn)擊的是按鈕 console.log(event.target === button && event.target === event.srcElement); // true // 當(dāng)我們點(diǎn)擊按鈕觸發(fā)的事件冒泡到了wrap,所以觸發(fā)了wrap的click事件, // 此時(shí)currentTarget指向的是wrap console.log(wrap===this && wrap === event.currentTarget); // true // 直接打印event然后控制臺(tái)中查看currentTaget會(huì)返回null // 你可以將他賦值到一個(gè)變量在打印輸出這個(gè)變量 // see https://github.com/vuejs/vue/issues/6867#issuecomment-338195468 })
在這個(gè)例子中,我們點(diǎn)擊頁面中的按鈕,然后再按鈕的包裹div中接收到了button冒泡上來的事件,這其中:
this 和 currentTarget指向的都是添加了監(jiān)聽器的對(duì)象這里就是wrap
target 和 srcElement指向的是觸發(fā)了事件的元素
事件委托也是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
簡(jiǎn)單理解event.srcElement === event.target.
Event.srcElement 是標(biāo)準(zhǔn)的 Event.target 屬性的一個(gè)別名。它只對(duì)老版本的IE瀏覽器有效。https://developer.mozilla.org...
參考之前的表格后看來這個(gè)屬性還沒有被干掉,在目前最新的瀏覽器上它依然存在,不過已經(jīng)不建議使用,除非你需要向下兼容.
完整的事件編程 EventTarget接口當(dāng)我們?cè)谑褂萌缦碌姆椒ǖ臅r(shí)候:
elem.addEventListener
elem.removeEventListener
elem.dispatchEvent
實(shí)際上是在使用EventTarget接口上的功能.
例如我們可以創(chuàng)建一個(gè)新的EventTarget對(duì)象來添加事件監(jiān)聽:
const a = new EventTarget; a.addEventListener("click",()=>{ })
但是這沒有任何意義,因?yàn)檫@里沒有事件被觸發(fā).我們僅僅是添加了事件監(jiān)聽器而已.
為了達(dá)到我們目的通過編程的方式來執(zhí)行完整的事件流程我們還需要完成如下的幾步:
繼承EventTarget而不是直接使用EventTarget的實(shí)例,
在事件監(jiān)聽函數(shù)中傳遞Event對(duì)象
找個(gè)地方來觸發(fā)這個(gè)事件
首先我們來繼承EventTarget對(duì)象:
繼承EventTarget在瀏覽器中大部分可以添加刪除事件的對(duì)象都繼承了EventTarget對(duì)象.
你可以在控制臺(tái)選擇一個(gè)HTML元素一路查找原型鏈得到.
但是他們進(jìn)過重重繼承,都有自己的獨(dú)特屬性和事件類型甚至是不同的構(gòu)造函數(shù).
為了和已有的事件進(jìn)行區(qū)分我們這里需要對(duì)EventTarget進(jìn)行繼承:
// --- 包裝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結(jié)束 // --- 創(chuàng)建我們繼承EventTarget的構(gòu)造函數(shù) function myElem() { } myElem.prototype = new MyEventTarget; myElem.prototype.constructor = myElem; // 創(chuàng)建實(shí)例 const instance = new myElem(); instance.addEventListener("click",()=>{ // 現(xiàn)在我們實(shí)例可以監(jiān)聽事件了 }); console.log(instance);
繼承的過程看似非常復(fù)雜,尤其是包裝EventTarget顯得多此一舉.但是搞定EventTarget的繼承確實(shí)花了我大量的時(shí)間去尋找解決方案.
你完全可以編寫自己的繼承方式來去繼承EventTarget,不過你會(huì)發(fā)現(xiàn)這其中的坑非常深.
簡(jiǎn)單來說,EventTarget在JavaScript中真的就是一個(gè)接口,雖然是以函數(shù)的形式存在,但是它不是構(gòu)造函數(shù)(這點(diǎn)在Chrome64 和firefox59后進(jìn)行了修改).
總之通過原型鏈繼承的EventTarget統(tǒng)統(tǒng)無法工作,如果使用ES6的類式繼承在現(xiàn)代瀏覽器中(Chrome64和firefox59后)可以使用class來進(jìn)行繼承.
詳細(xì)參考:創(chuàng)建我們的Event對(duì)象https://stackoverflow.com/que...
獲取一個(gè)event:
document.getElementById("button").addEventListener("click",(event)=>{ // event console.log(event); })
如果你在瀏覽器中運(yùn)行這段代碼并且在控制臺(tái)中查看,你會(huì)發(fā)現(xiàn)變量event的名稱MouseEvent,如果你沿著原型鏈向上你會(huì)發(fā)現(xiàn)繼承的是UIEvent再次向上查看則是真正的Event.
事件觸發(fā)中傳遞的第一個(gè)參數(shù)我們通常叫它event,所有的event對(duì)象都基于Event,但是這不意味著這種關(guān)系的窗戶紙就只有一層,click事件中的event和Event之間就隔著一個(gè)UIEvent.
通常隨著event繼承的層數(shù)越多,event對(duì)象身上的屬性也會(huì)越來越多.
現(xiàn)在我們來創(chuàng)建一個(gè)標(biāo)準(zhǔn)的Event對(duì)象:
// 使用全局的Event new Event("test",{ // 事件類型 bubbles:false, // 是否冒泡 默認(rèn)false cancelable:false,// 是否可以被取消 默認(rèn)false });
https://developer.mozilla.org...
如果你在瀏覽器中觀察這個(gè)對(duì)象,你會(huì)發(fā)現(xiàn)事件上常見的屬性諸如:
event.target
event.currentTarget
event.preventDefault()
都在這個(gè)new Event()返回的對(duì)象中,由于其他類型的事件都繼承自Event這也解釋了為什么事件對(duì)象中總是有這些屬性.
和繼承EventTarget一樣,使用Event的過程也同樣艱難,總的來說使用Event的難點(diǎn)在于它有兩套API:
第一套比較新的API提供了現(xiàn)代的接口,也就是之前例子中的方式.
在創(chuàng)建一個(gè)已有的事件的時(shí)候,你只需要使用全局的構(gòu)造函數(shù)就可以,
例如:new MouseEvent("test",/*對(duì)應(yīng)MouseEvent的參數(shù)選項(xiàng)*/),
但是缺點(diǎn)就是不支持IE瀏覽器.
第二套API支持IE瀏覽器,但是使用過程比較繁瑣
使用Event.createEvent(/*事件類型*/)創(chuàng)建對(duì)應(yīng)事件類型的Event對(duì)象,
使用Event.initEvent()來初始化事件,并且提供對(duì)應(yīng)事件類型的參數(shù),
如果你創(chuàng)建一個(gè)MouseEvent類型的事件InitEvent方法最多需要15個(gè)參數(shù).
這種情況下使用new MouseEvent()傳入對(duì)象配置的形式就簡(jiǎn)單多了.
一篇值得參考的文章,使用createEvent apihttps://www.cnblogs.com/ggz19...
此外不同種類的事件,都有自己的全局構(gòu)造函數(shù),不同類型的構(gòu)造函數(shù)的第二個(gè)參數(shù)中的選項(xiàng)也是不同的.
其他的構(gòu)造函數(shù)請(qǐng)參考這里.
觸發(fā)我們的事件觸發(fā)事件就顯得簡(jiǎn)單多了,我們需要使用EventTarget.dispatchEvent方法.
在我們之前創(chuàng)建的實(shí)例上進(jìn)行事件的觸發(fā):
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); // 監(jiān)聽事件并且打印實(shí)例 }); const myEvent = new Event("test"); // 創(chuàng)建Event實(shí)例 instance.dispatchEvent(myEvent); // 觸發(fā)事件
當(dāng)你調(diào)用dispatchEvent的時(shí)候,EventTarget會(huì)按照對(duì)應(yīng)事件注冊(cè)的順序來同步執(zhí)行這些事件監(jiān)聽器.
如果在事件監(jiān)聽器中調(diào)用了event.preventDefault,那么dispatchEvent就返回false反之返回true(前提是cancleable為true).
詳細(xì)參考編程式的事件觸發(fā)https://developer.mozilla.org...
我們?cè)陧撁嬷衼硪淮尉唧w的實(shí)戰(zhàn),首先建立如下的HTML結(jié)構(gòu):
我們?cè)?wrap中監(jiān)聽click事件,然后在#button觸發(fā)click事件.
這樣我們可以練習(xí)一下Event中bubbles(允許冒泡)參數(shù)的使用,
另外還可以測(cè)試click事件中的Event對(duì)象如果不是MouseEvent的實(shí)例那么監(jiān)聽器是否會(huì)被觸發(fā).
const button = document.getElementById("button"), wrap = document.getElementById("wrap"); wrap.addEventListener("click", (event) => { console.log(event); // 打印event對(duì)象 }); const myEvent1 = new Event("click", { bubbles: false, // 不可以冒泡 }); const myEvent2 = new Event("click", { bubbles: true, // 可以冒泡 }); button.dispatchEvent(myEvent1); // 這次沒有打印出內(nèi)容 button.dispatchEvent(myEvent2); // 這次打印出了內(nèi)容
結(jié)論很明確:
dispatchEvent執(zhí)行的時(shí)候只要是Event的實(shí)例且類型相同那么監(jiān)聽器就會(huì)被觸發(fā).
bubbles參數(shù)可以控制該事件是否允許冒泡
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/109251.html
前言 在平常開發(fā)過程中,就算不使用現(xiàn)在主流的框架也至少得使用個(gè)Jquery,這些工具幫我們統(tǒng)一不同瀏覽器平臺(tái)之間的差異和細(xì)節(jié),可以將注意力集中到開發(fā)上來. 不過有意思的一點(diǎn)是,在看完高程的N年后我居然連event對(duì)象中的target和currentTarget屬性的區(qū)別都忘記了. 先提幾個(gè)引子: 你能說出event.currentTarget和event.target的區(qū)別嗎? 如果可以那么ev...
摘要:回調(diào)函數(shù),一般在同步情境下是最后執(zhí)行的,而在異步情境下有可能不執(zhí)行,因?yàn)槭录]有被觸發(fā)或者條件不滿足。同步方式請(qǐng)求異步同步請(qǐng)求當(dāng)請(qǐng)求開始發(fā)送時(shí),瀏覽器事件線程通知主線程,讓線程發(fā)送數(shù)據(jù)請(qǐng)求,主線程收到 一直以來都知道JavaScript是一門單線程語言,在筆試過程中不斷的遇到一些輸出結(jié)果的問題,考量的是對(duì)異步編程掌握情況。一般被問到異步的時(shí)候腦子里第一反應(yīng)就是Ajax,setTimse...
摘要:事件發(fā)生后,對(duì)象可能會(huì)作出響應(yīng),也有可能無動(dòng)于衷。事件模型在講解事件模型前,再用一個(gè)例子作為引入。當(dāng)一個(gè)事件發(fā)生時(shí),事件會(huì)在樹中進(jìn)行傳播。冒泡階段在此階段,事件從事件源開始向上傳播,直到根結(jié)點(diǎn)。 1.何為DOM DOM是Document Object Model的縮寫,中文譯為文檔對(duì)象模型。它是一種跨平臺(tái)、跨語言的編程接口,將HTML,XHTML,XML文檔映射成樹形結(jié)構(gòu),樹的每一個(gè)節(jié)...
摘要:事件發(fā)生后,對(duì)象可能會(huì)作出響應(yīng),也有可能無動(dòng)于衷。事件模型在講解事件模型前,再用一個(gè)例子作為引入。當(dāng)一個(gè)事件發(fā)生時(shí),事件會(huì)在樹中進(jìn)行傳播。冒泡階段在此階段,事件從事件源開始向上傳播,直到根結(jié)點(diǎn)。 1.何為DOM DOM是Document Object Model的縮寫,中文譯為文檔對(duì)象模型。它是一種跨平臺(tái)、跨語言的編程接口,將HTML,XHTML,XML文檔映射成樹形結(jié)構(gòu),樹的每一個(gè)節(jié)...
閱讀 3603·2021-11-24 10:25
閱讀 2508·2021-11-24 09:38
閱讀 1217·2021-09-08 10:41
閱讀 2904·2021-09-01 10:42
閱讀 2569·2021-07-25 21:37
閱讀 1981·2019-08-30 15:56
閱讀 914·2019-08-30 15:55
閱讀 2750·2019-08-30 15:54