摘要:事件監(jiān)聽函數(shù)標(biāo)準(zhǔn)的事件監(jiān)聽函數(shù)如下上面的示例表示先獲得表示節(jié)點(diǎn)的對(duì)象,然后在這個(gè)對(duì)象上面添加了一個(gè)事件監(jiān)聽器,當(dāng)監(jiān)聽到事件發(fā)生時(shí),則調(diào)用回調(diào)函數(shù),即在控制臺(tái)輸出。
JavaScript 事件解讀 1. 事件基本概念
事件是指在文檔或者瀏覽器中發(fā)生的一些特定交互瞬間,比如打開某一個(gè)網(wǎng)頁(yè),瀏覽器加載完成后會(huì)觸發(fā) load 事件,當(dāng)鼠標(biāo)懸浮于某一個(gè)元素上時(shí)會(huì)觸發(fā) hover 事件,當(dāng)鼠標(biāo)點(diǎn)擊某一個(gè)元素時(shí)會(huì)觸發(fā) click 事件等等。
事件處理就是當(dāng)事件被觸發(fā)后,瀏覽器響應(yīng)這個(gè)事件的行為,而這個(gè)行為所對(duì)應(yīng)的代碼即為事件處理程序。
2. 事件操作:監(jiān)聽與移除監(jiān)聽 2.1 監(jiān)聽事件瀏覽器會(huì)根據(jù)一些事件作出相對(duì)應(yīng)的事件處理,事件處理的前提是需要監(jiān)聽事件,監(jiān)聽事件的方法主要有以下三種:
2.1.1 HTML 內(nèi)聯(lián)屬性即在 HTML 元素里直接填寫與事件相關(guān)的屬性,屬性值為事件處理程序。示例如下:
onclick 對(duì)應(yīng)著 click 事件,所以當(dāng)按鈕被點(diǎn)擊后,便會(huì)執(zhí)行事件處理程序,即控制臺(tái)輸出 You clicked me!。
不過(guò)我們需要指出的是,這種方式將 HTML 代碼與 JavaScript 代碼耦合在一起,不利于代碼的維護(hù),所以應(yīng)該盡量避免使用這樣的方式。
2.1.2 DOM 屬性綁定通過(guò)直接設(shè)置某個(gè) DOM 節(jié)點(diǎn)的屬性來(lái)指定事件和事件處理程序,上代碼:
const btn = document.getElementById("btn"); btn.onclick = function(e) { console.log("You clicked me!"); };
上面示例中,首先獲得 btn 這個(gè)對(duì)象,通過(guò)給這個(gè)對(duì)象添加 onclick 屬性的方式來(lái)監(jiān)聽 click 事件,這個(gè)屬性值對(duì)應(yīng)的就是事件處理程序。這段程序也被稱作 DOM 0 級(jí)事件處理程序。
2.1.3 事件監(jiān)聽函數(shù)標(biāo)準(zhǔn)的事件監(jiān)聽函數(shù)如下:
const btn = document.getElementById("btn"); btn.addEventListener("click", () => { console.log("You clicked me!"); }, false);
上面的示例表示先獲得表示節(jié)點(diǎn)的 btn 對(duì)象,然后在這個(gè)對(duì)象上面添加了一個(gè)事件監(jiān)聽器,當(dāng)監(jiān)聽到 click 事件發(fā)生時(shí),則調(diào)用回調(diào)函數(shù),即在控制臺(tái)輸出 You clicked me!。addEventListener 函數(shù)包含了三個(gè)參數(shù) false,第三個(gè)參數(shù)的含義在后面的事件觸發(fā)三個(gè)階段之后再講解。這段程序也被稱作 DOM 2 級(jí)事件處理程序。IE9+、FireFox、Safari、Chrome 和 Opera 都是支持 DOM 2 級(jí)事件處理程序的,對(duì)于 IE8 及以下版本,則用 attacEvent() 函數(shù)綁定事件。
所以我們可以寫一段具有兼容性的代碼:
function addEventHandler(obj, eventName, handler) { if (document.addEventListener) { obj.addEventListener(eventName, handler, false); } else if (document.attachEvent) { obj.attachEvent("on" + eventName, handler); } else { obj["on" + eventName] = handler; } }2.2 移除事件監(jiān)聽
在為某個(gè)元素綁定了一個(gè)事件后,如果想接觸綁定,則需要用到 removeEventListener 方法??慈缦吕樱?/p>
const handler = function() { // handler logic } const btn = document.getElementById("btn"); btn.addEventListener("click", handler); btn.removeEventListener("click", handler);
需要注意的是,綁定事件的回調(diào)函數(shù)不能是匿名函數(shù),必須是一個(gè)已經(jīng)被聲明的函數(shù),因?yàn)榻獬录壎〞r(shí)需要傳遞這個(gè)回調(diào)函數(shù)的引用。
同樣,IE8 及以下版本也不支持上面的方法,而是用 detachEvent 代替。
const handler = function() { // handler logic } const btn = document.getElementById("btn"); btn.attachEvent("onclick", handler); btn.detachEvent("onclick", handler);
同樣,可以寫一段具有兼容性的刪除事件函數(shù):
function removeEventHandler(obj, eventName, handler) { if (document.removeEventListener) { obj.removeEventListener(eventName, handler, false); } else if (document.detachEvent) { obj,detachEvent("on" + eventName, handler); } else { obj["on" + eventName] = null; } }3. 事件觸發(fā)過(guò)程
事件流描述了頁(yè)面接收事件的順序?,F(xiàn)代瀏覽器(指 IE6-IE8 除外的瀏覽器,包括 IE9+、FireFox、Safari、Chrome 和 Opera 等)事件流包含三個(gè)過(guò)程,分別是捕獲階段、目標(biāo)階段和冒泡階段,下圖形象地說(shuō)明這個(gè)過(guò)程:
下面就詳細(xì)地講解這三個(gè)過(guò)程。
3.1 捕獲階段當(dāng)我們對(duì) DOM 元素進(jìn)行操作時(shí),比如鼠標(biāo)點(diǎn)擊、懸浮等,就會(huì)有一個(gè)事件傳輸?shù)竭@個(gè) DOM 元素,這個(gè)事件從 Window 開始,依次經(jīng)過(guò) docuemnt、html、body,再不斷經(jīng)過(guò)子節(jié)點(diǎn)直到到達(dá)目標(biāo)元素,從 Window 到達(dá)目標(biāo)元素父節(jié)點(diǎn)的過(guò)程稱為捕獲階段,注意此時(shí)還未到達(dá)目標(biāo)節(jié)點(diǎn)。
3.2 目標(biāo)階段捕獲階段結(jié)束時(shí),事件到達(dá)了目標(biāo)節(jié)點(diǎn)的父節(jié)點(diǎn),最終到達(dá)目標(biāo)節(jié)點(diǎn),并在目標(biāo)節(jié)點(diǎn)上觸發(fā)了這個(gè)事件,這就是目標(biāo)階段。
需要注意的是,事件觸發(fā)的目標(biāo)節(jié)點(diǎn)為最底層的節(jié)點(diǎn)。比如下面的例子:
你猜,目標(biāo)在這里還是那里。
當(dāng)我們點(diǎn)擊“那里”的時(shí)候,目標(biāo)節(jié)點(diǎn)是,點(diǎn)擊“這里”的時(shí)候,目標(biāo)節(jié)點(diǎn)是
,而當(dāng)我們點(diǎn)擊區(qū)域之外,區(qū)域之內(nèi)時(shí),目標(biāo)節(jié)點(diǎn)就是。 3.3 冒泡階段當(dāng)事件到達(dá)目標(biāo)節(jié)點(diǎn)之后,就會(huì)沿著原路返回,這個(gè)過(guò)程有點(diǎn)類似水泡從水底浮出水面的過(guò)程,所以稱這個(gè)過(guò)程為冒泡階段。
針對(duì)這個(gè)過(guò)程,wilsonpage 做了一個(gè) DEMO,可以非常直觀地查看這個(gè)過(guò)程。
現(xiàn)在再看 addEventListener(eventName, handler, useCapture) 函數(shù)。第三個(gè)參數(shù)是 useCapture,代表是否在捕獲階段進(jìn)行事件處理, 如果是 false, 則在冒泡階段進(jìn)行事件處理,如果是 true,在捕獲階段進(jìn)行事件處理,默認(rèn)是 false。這么設(shè)計(jì)的主要原因是當(dāng)年微軟和 netscape 之間的瀏覽器戰(zhàn)爭(zhēng)打得火熱,netscape 主張捕獲方式,微軟主張冒泡方式,W3C 采用了折中的方式,即先捕獲再冒泡。
4、事件委托上面我們講了事件的冒泡機(jī)制,我們可以利用這一特性來(lái)提高頁(yè)面性能,事件委托便事件冒泡是最典型的應(yīng)用之一。
何謂“委托”?在現(xiàn)實(shí)中,當(dāng)我們不想做某件事時(shí),便“委托”給他人,讓他人代為完成。JavaScript 中,事件的委托表示給元素的父級(jí)或者祖級(jí),甚至頁(yè)面,由他們來(lái)綁定事件,然后利用事件冒泡的基本原理,通過(guò)事件目標(biāo)對(duì)象進(jìn)行檢測(cè),然后執(zhí)行相關(guān)操作??聪旅胬樱?/p>
// HTML
上面的例子中,5 個(gè)列表項(xiàng)的點(diǎn)擊事件均委托給了父元素 。
先看看事件委托的可行性。有人會(huì)問(wèn),當(dāng)事件不是加在某個(gè)元素上的,如何在這個(gè)元素上觸發(fā)事件呢?我們就是利用事件冒泡的機(jī)制,事件流到達(dá)目標(biāo)元素后會(huì)向上冒泡,此時(shí)父元素接收到事件流便會(huì)執(zhí)行事件執(zhí)行程序。有人又會(huì)問(wèn),被委托的父元素下面如果有很多子元素,怎么知道事件流來(lái)自于哪個(gè)子元素呢?這個(gè)我們可以從事件對(duì)象中的 target 屬性獲得。事件對(duì)象下面會(huì)詳細(xì)講解。
我們?cè)賮?lái)看看為什么需要事件委托。
減少事件綁定。上面的例子中,也可以分別給每個(gè)列表項(xiàng)綁定事件,但利用事件委托的方式不僅省去了一一綁定的麻煩,也提升了網(wǎng)頁(yè)的性能,因?yàn)槊拷壎ㄒ粋€(gè)事件便會(huì)增加內(nèi)存使用。
可以動(dòng)態(tài)監(jiān)聽綁定。上面的例子中,我們對(duì) 5 個(gè)列表項(xiàng)進(jìn)行了事件監(jiān)聽,當(dāng)刪除一個(gè)列表項(xiàng)時(shí)不需要多帶帶刪除這個(gè)列表項(xiàng)所綁定的事件,而增加一個(gè)列表項(xiàng)時(shí)也不需要多帶帶為新增項(xiàng)綁定事件。
看了上面的例子和解釋,我們可以看出事件委托的核心就是監(jiān)聽一個(gè) DOM 中更高層、更不具體的元素,等到事件冒泡到這個(gè)不具體元素時(shí),通過(guò) event 對(duì)象的 target 屬性來(lái)獲取觸發(fā)事件的具體元素。
5、阻止事件冒泡事件委托是事件冒泡的一個(gè)應(yīng)用,但有時(shí)候我們并不希望事件冒泡。比如下面的例子:
const ele = document.getElementById("ele"); ele.addEventListener("click", function() { console.log("ele-click"); }, false); document.addEventListener("click", function() { console.log("document-click"); }, false);
我們本意是當(dāng)點(diǎn)擊 ele 元素區(qū)域時(shí)顯示 "ele-click",點(diǎn)擊其他區(qū)域時(shí)顯示 "document-click"。但是我們發(fā)現(xiàn)點(diǎn)擊 ele 元素區(qū)域時(shí)會(huì)依次顯示 "ele-click" "document-click"。那是因?yàn)榻壎ㄔ?ele 上的事件冒泡到了 document 上。想要解決這個(gè)問(wèn)題,只需要加一行代碼:
const ele = document.getElementById("ele"); ele.addEventListener("click", function(e) { console.log("ele-click"); e.stopPropagation(); // 阻止事件冒泡 }, false); document.addEventListener("click", function(e) { console.log("document-click"); }, false);
我們還能用 e.cancelBubble = true 來(lái)替代 e.stopPropagation()。網(wǎng)上的說(shuō)法是 cancelBubble 僅僅適用于 IE,而 stopPropagation 適用于其他瀏覽器。但根據(jù)我實(shí)驗(yàn)的結(jié)果,現(xiàn)代瀏覽器(IE9 及 以上、Chrome、FF 等)均同時(shí)支持這兩種寫法。為了保險(xiǎn)起見,我們可以采用以下代碼:
function preventBubble(e) { if (!e) { const e = window.event; } e.cancelBubble = true; if (e.stopPropagation) { e.stopPropagation(); } }6、event 對(duì)象
Event 對(duì)象代表事件的狀態(tài),比如事件在其中發(fā)生的元素、鍵盤按鍵的狀態(tài)、鼠標(biāo)的位置、鼠標(biāo)按鈕的狀態(tài)。當(dāng)一個(gè)事件被觸發(fā)的時(shí)候,就會(huì)創(chuàng)建一個(gè)事件對(duì)象。
我們用下面的代碼打印出事件對(duì)象:
Item 1 Item 2
chrome 49 的運(yùn)行結(jié)果如下:
下面介紹一些比較常用的屬性和方法。
target、 srcElement、 currentTarget 和 relatedTarget、fromElement、 toElementtarget 與 srcElement 完全相同;
target 指觸發(fā)事件的元素, currentTarget 指事件所綁定的元素;
relatedTarget: 與事件的目標(biāo)節(jié)點(diǎn)相關(guān)的節(jié)點(diǎn)。對(duì)于 mouseover 事件來(lái)說(shuō),該屬性是鼠標(biāo)指針移到目標(biāo)節(jié)點(diǎn)上時(shí)所離開的那個(gè)節(jié)點(diǎn)。對(duì)于 mouseout 事件來(lái)說(shuō),該屬性是離開目標(biāo)時(shí),鼠標(biāo)指針進(jìn)入的節(jié)點(diǎn)。
對(duì)于其他類型的事件來(lái)說(shuō),這個(gè)屬性沒(méi)有用;
fromElement 和 toElement 僅僅對(duì)于 mouseover 和 mouseout 事件有效。
以上面的例子說(shuō)明,當(dāng)點(diǎn)擊
上圖:
offsetX/Y: 點(diǎn)擊位置相對(duì)于所處元素左上角的位置;
clientX/Y: 點(diǎn)擊位置相對(duì)于瀏覽器內(nèi)容區(qū)域左上角的位置;
screenX/Y: 點(diǎn)擊位置相對(duì)于屏幕左上角的位置;
pageX/Y: 點(diǎn)擊位置相對(duì)整張頁(yè)面左上角的位置;
pageX/Y 與 clientX/Y 一般情況下會(huì)相同,只有出現(xiàn)滾動(dòng)條時(shí)才不一樣。
altKey、 ctrlKey、 shiftKeyaltKey: 返回當(dāng)事件被觸發(fā)時(shí),"ALT" 是否被按下;
ctrlKey: 返回當(dāng)事件被觸發(fā)時(shí),"CTRL" 鍵是否被按下;
shiftKey: 返回當(dāng)事件被觸發(fā)時(shí),"SHIFT" 鍵是否被按下;
其他屬性type: 返回當(dāng)前 Event 對(duì)象表示的事件的名稱
bubbles: 返回布爾值,指示事件是否是起泡事件類型;
cancelable: 返回布爾值,指示事件是否可擁可取消的默認(rèn)動(dòng)作;
eventPhase: 返回事件傳播的當(dāng)前階段,有三個(gè)值: Event.CAPTURING_PHASE、 Event.AT_TARGET、 Event.BUBBLING_PHASE,對(duì)應(yīng)的值為 1、2、3,分別表示捕獲階段、正常事件派發(fā)和起泡階段;
path: 冒泡階段經(jīng)過(guò)的節(jié)點(diǎn);
方法preventDefault(): 通知瀏覽器不要執(zhí)行與事件關(guān)聯(lián)的默認(rèn)動(dòng)作;
stopPropagation(): 阻止冒泡;
參考及拓展閱讀:
HTML DOM Event 對(duì)象
最詳細(xì)的JavaScript和事件解讀
JavaScript Events
[解惑]JavaScript事件機(jī)制
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/79308.html
摘要:個(gè)人前端文章整理從最開始萌生寫文章的想法,到著手開始寫,再到現(xiàn)在已經(jīng)一年的時(shí)間了,由于工作比較忙,更新緩慢,后面還是會(huì)繼更新,現(xiàn)將已經(jīng)寫好的文章整理一個(gè)目錄,方便更多的小伙伴去學(xué)習(xí)。 showImg(https://segmentfault.com/img/remote/1460000017490740?w=1920&h=1080); 個(gè)人前端文章整理 從最開始萌生寫文章的想法,到著手...
摘要:機(jī)制詳解與中實(shí)踐應(yīng)用歸納于筆者的現(xiàn)代開發(fā)語(yǔ)法基礎(chǔ)與實(shí)踐技巧系列文章。事件循環(huán)機(jī)制詳解與實(shí)踐應(yīng)用是典型的單線程單并發(fā)語(yǔ)言,即表示在同一時(shí)間片內(nèi)其只能執(zhí)行單個(gè)任務(wù)或者部分代碼片。 JavaScript Event Loop 機(jī)制詳解與 Vue.js 中實(shí)踐應(yīng)用歸納于筆者的現(xiàn)代 JavaScript 開發(fā):語(yǔ)法基礎(chǔ)與實(shí)踐技巧系列文章。本文依次介紹了函數(shù)調(diào)用棧、MacroTask 與 Micr...
摘要:布爾值表示捕獲階段調(diào)用事件處理程序,表示冒泡階段通過(guò)對(duì)象的方法,也可以定義事件的回調(diào)函數(shù)。對(duì)象會(huì)被作為第一個(gè)參數(shù)傳遞給事件監(jiān)聽的回調(diào)函數(shù)。布爾默認(rèn)值是,當(dāng)設(shè)置成時(shí)用以取消事件的默認(rèn)行為與中的相同。 其實(shí)這篇文章挺早之前就寫了,但是由于sf保存方面的bug,所以當(dāng)時(shí)寫了一大堆,結(jié)果沒(méi)保存,覺得這個(gè)沒(méi)寫完是個(gè)不小的遺憾,今天正好有空,就給補(bǔ)充下了,也正好給我的javascript學(xué)習(xí)總結(jié)做...
摘要:響應(yīng)某個(gè)事件的函數(shù)就叫事件處理程序或事件偵聽器。為事件指定事件處理程序的方法主要有種。事件處理程序事件直接加在元素上。事件委托利用冒泡的原理,把事件加到父元素或祖先元素上,觸發(fā)執(zhí)行效果,解決事件處理程序過(guò)多問(wèn)題。事件委托優(yōu)點(diǎn)提高性能。 JavaScript簡(jiǎn)單入門可以看看我丑丑的Github博客JavaScript簡(jiǎn)單入門 事件 JavaScript與HTML之間的交互是通過(guò)事件實(shí)現(xiàn)的...
摘要:從異步過(guò)程的角度看,函數(shù)就是異步過(guò)程的發(fā)起函數(shù),事件監(jiān)聽函數(shù)就是異步過(guò)程的回調(diào)函數(shù)。事件觸發(fā)時(shí),表示異步任務(wù)完成,會(huì)將事件監(jiān)聽器函數(shù)封裝成一條消息放到消息隊(duì)列中,等待主線程執(zhí)行。 1.為什么JavaScript是單線程? JavaScript語(yǔ)言的一大特點(diǎn)就是單線程,也就是說(shuō),同一個(gè)時(shí)間只能做一件事。那么,為什么JavaScript不能有多個(gè)線程呢?這樣能提高效率啊。JavaScrip...
閱讀 1719·2021-11-25 09:43
閱讀 2681·2019-08-30 15:53
閱讀 1832·2019-08-30 15:52
閱讀 2911·2019-08-29 13:56
閱讀 3333·2019-08-26 12:12
閱讀 576·2019-08-23 17:58
閱讀 2151·2019-08-23 16:59
閱讀 945·2019-08-23 16:21