Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
件處理
客戶端js程序采用異步事件驅(qū)動(dòng)編程模型。在這種情況下當(dāng)文檔,瀏覽器,元素發(fā)生一些事情的時(shí)候,會(huì)產(chǎn)生事件。
舉例 當(dāng)瀏覽器加載完文檔以后會(huì)觸發(fā)一個(gè)事件。該事件會(huì)有一個(gè)函數(shù)進(jìn)行處理,即回調(diào)函數(shù)
這種只不單單用于web界面,所有使用圖形界面的應(yīng)用程序都采用了這種方式。
事件分類
依賴于設(shè)備的輸入事件
有些事件和特定輸入設(shè)備直接相關(guān)。比如鼠標(biāo)和鍵盤。
touchmove 當(dāng)觸點(diǎn)在觸控平面上時(shí)發(fā)生該事件
獨(dú)立于設(shè)備的輸入事件
click事件表示激活了鏈接的事件。通過鼠標(biāo),按鈕或者移動(dòng)設(shè)備上的觸摸觸發(fā)該事件
用戶界面事件
通常用于HTML表單元素,包括文本輸入域獲取鍵盤焦點(diǎn)的focus事件,提交按鈕將會(huì)觸發(fā)submit事件
狀態(tài)變化事件
不是由用戶活動(dòng)由網(wǎng)絡(luò)或者瀏覽器活動(dòng)觸發(fā),表示某種生命周期或相關(guān)狀態(tài)的變化。
online 返回瀏覽器的聯(lián)網(wǎng)狀態(tài)
特定的api事件
一些web api會(huì)有自己的事件類型
拖放的api dragstart 當(dāng)用戶拖動(dòng)一個(gè)元素,或者選擇一個(gè)文本的時(shí)候觸發(fā)該事件
計(jì)時(shí)器和錯(cuò)誤處理程序
計(jì)時(shí)器在指定的時(shí)間后觸發(fā)該事件,錯(cuò)誤處理程序,try catch 對(duì)應(yīng)于一個(gè)響應(yīng),會(huì)有異步進(jìn)行拋出
傳統(tǒng)事件類型
表單事件
當(dāng)提交表單和重置表單時(shí)會(huì)觸發(fā)submit和reset事件,當(dāng)用戶和類按鈕(包括單選和復(fù)選)交互的時(shí),將會(huì)發(fā)生click事件,當(dāng)用戶輸入文字,選擇選項(xiàng)或選擇復(fù)選框改變相應(yīng)的表單元素的狀態(tài)時(shí),將會(huì)觸發(fā)change事件,通過鍵盤改變焦點(diǎn)的表單元素在得到和失去焦點(diǎn)時(shí)將會(huì)觸發(fā)focus和blur事件。
通過事件處理程序能取消submit和reset事件的默認(rèn)操作。某些click事件也是如此,focus和clur事件不會(huì)冒泡,但其他所有表單事件都可以。
無論用戶何時(shí)輸入文字,都會(huì)觸發(fā)input事件。
window事件
window事件是指事件的發(fā)生與瀏覽器窗口本身而非窗口中顯示的任何特定文檔內(nèi)容相關(guān)。
load事件
load事件與文檔和其所有外部資源(圖片)完全加載并顯示給用戶時(shí)將會(huì)觸發(fā)。
unload事件
unload事件,當(dāng)用戶離開當(dāng)前文檔轉(zhuǎn)向其他文檔時(shí)將會(huì)觸發(fā)。
unload事件處理程序可以用于保存用戶的狀態(tài),但其不能取消用戶轉(zhuǎn)向其他地方。
beforeunload事件
此事件將會(huì)詢問用戶是否確定離開當(dāng)前頁(yè)面。如果beforeunload的回調(diào)函數(shù)返回一個(gè)字符串,那么在新頁(yè)面加載之前,字符串將會(huì)出現(xiàn)在展示給用戶確認(rèn)的對(duì)話框上,這樣用戶將會(huì)有機(jī)會(huì)取消跳轉(zhuǎn)停留在當(dāng)前頁(yè)上
注意;該事件僅僅是在當(dāng)前頁(yè)面的跳轉(zhuǎn)更改等,轉(zhuǎn)換標(biāo)簽不會(huì)觸發(fā)該事件
onerror屬性
此為一個(gè)window對(duì)象的屬性。在js出錯(cuò)的時(shí)候?qū)?huì)觸發(fā)其
其他
像img元素這樣的替換元素也能為其注冊(cè)load和error事件處理程序。當(dāng)外部資源完全加載或發(fā)生阻塞加載錯(cuò)誤時(shí)將會(huì)觸發(fā)該事件。某些瀏覽器也支持about事件,當(dāng)圖強(qiáng)因?yàn)橛脩敉V辜虞d進(jìn)程而導(dǎo)致失敗的時(shí)候也會(huì)觸發(fā)該事件。
focus和blur事件也為window事件,當(dāng)瀏覽器窗口從操作系統(tǒng)中得到或失去鍵盤焦點(diǎn)的時(shí)候?qū)?huì)觸發(fā)該事件
當(dāng)用戶調(diào)整瀏覽器窗口大小或滾動(dòng)其會(huì)觸發(fā)resize和scroll事件,scroll事件也能在任何可以滾動(dòng)的文檔元素上觸發(fā),例如css的的overflow屬性也能觸發(fā)。
鼠標(biāo)事件
當(dāng)用戶在文檔上移動(dòng)和單擊鼠標(biāo)時(shí)都會(huì)產(chǎn)生鼠標(biāo)事件。這些事件在鼠標(biāo)指針?biāo)鶎?duì)應(yīng)的最深嵌套元素上觸發(fā)。但其會(huì)冒泡直到文檔的最頂層。
clientX和clientY屬性指定了鼠標(biāo)在窗口坐標(biāo)中的位置。button和which屬性指定了按下的鼠標(biāo)鍵是哪個(gè)。
當(dāng)鼠標(biāo)輔助鍵按下的時(shí)候,對(duì)應(yīng)的屬性為altkey和ctrlkey和metakey和shiftkey會(huì)設(shè)置為true,對(duì)于click事件,detail屬性指定了其是單擊,雙擊,還是三擊。
每當(dāng)用戶移動(dòng)和拖動(dòng)鼠標(biāo)時(shí),會(huì)觸發(fā)mousemove事件,當(dāng)用戶按下或釋放鼠標(biāo)按鍵的時(shí)候觸發(fā)mousedown和mouseup事件。
在mousedown和mouseup事件隊(duì)列之后,瀏覽器也會(huì)觸發(fā)click事件,如果用戶在很短的時(shí)間內(nèi)單擊兩次鼠標(biāo),則第二個(gè)事件為dblclic事件,當(dāng)單擊鼠標(biāo)右鍵時(shí),瀏覽器會(huì)顯示上下文菜單,在顯示菜單之前,也會(huì)觸發(fā)contextmenu事件,如果取消這個(gè)事件將會(huì)阻止菜單的顯示,該事件為獲得鼠標(biāo)右擊通知的最簡(jiǎn)單方法。
當(dāng)用戶移動(dòng)鼠標(biāo)指針從而使它懸停到新元素上時(shí),瀏覽器就會(huì)在該元素上觸發(fā)mouseover事件,當(dāng)鼠標(biāo)移動(dòng)指針從而使它不在懸停在某個(gè)元素上時(shí),瀏覽器會(huì)觸發(fā)mouseout事件,(該事件有relatedTarget屬性指明這個(gè)過程涉及的其他元素)
當(dāng)用戶滾動(dòng)鼠標(biāo)的時(shí)候,瀏覽器觸發(fā)mousewheel事件,傳遞事件對(duì)象屬性指定輪子轉(zhuǎn)動(dòng)的大小和方向。
鍵盤事件
當(dāng)鍵盤聚焦到web瀏覽器時(shí),用戶每次按下或釋放鍵盤上的按鍵時(shí)都會(huì)產(chǎn)生事件,鍵盤快捷鍵葉同樣能被瀏覽器和操作系統(tǒng)吃掉,此時(shí)對(duì)js事件處理程序不可見,無論任何文檔元素獲取鍵盤焦點(diǎn)都會(huì)觸發(fā)鍵盤事件,并會(huì)冒泡到window對(duì)象,
觸摸屏和移動(dòng)設(shè)備事件
用戶旋轉(zhuǎn)設(shè)備的時(shí)會(huì)產(chǎn)生orientationchange事件,有一個(gè)縮放和旋轉(zhuǎn)手勢(shì),當(dāng)手勢(shì)開始時(shí)將會(huì)生成getsturestart事件,手勢(shì)結(jié)束時(shí)將會(huì)生成gestureend事件。在這兩個(gè)事件之間是跟蹤手勢(shì)過程的gesturechange事件隊(duì)列,將事件傳遞的事件對(duì)象屬性為scale和rotation
握緊手勢(shì)的scale值小于1.0
撐開手勢(shì)的scale的值大于1.0
rotation為事件開始時(shí)手指旋轉(zhuǎn)的角度。以度為單位正值表示順時(shí)針方向旋轉(zhuǎn)
當(dāng)手指觸摸屏幕的時(shí)候?qū)?huì)觸發(fā)touchstart事件,當(dāng)手指移動(dòng)時(shí)會(huì)觸發(fā)touchmove事件,當(dāng)手指離開屏幕時(shí)會(huì)觸發(fā)touchend事件,觸摸事件傳遞的事件對(duì)象有一個(gè)changedTouches屬性,該屬性為一個(gè)類數(shù)組對(duì)象,其每個(gè)元素都描述觸摸的位置。
當(dāng)設(shè)備允許用戶從豎屏旋轉(zhuǎn)到橫屏模式時(shí)會(huì)在window對(duì)象上觸發(fā)orientationchanged事件。該對(duì)象的orientation屬性能給出當(dāng)前方位,其值為0, 90, 180, 或 -90
注冊(cè)事件處理程序
設(shè)置js對(duì)象屬性為事件處理程序
事件處理程序?qū)傩缘拿钟蒾n后面跟著事件名組成。onclick,onchange,onload,onmouseover
onload 當(dāng)對(duì)象的資源被加載的時(shí)候,該對(duì)象的onload事件將會(huì)被觸發(fā),然后將表單的提交的onsubmit事件和一個(gè)處理函數(shù)進(jìn)行綁定
onsubmit 在表單提交的時(shí)候,將會(huì)觸發(fā)該事件
下方栗子演示一個(gè)提交的時(shí)候表單驗(yàn)證的過程
其中validate函數(shù)為一個(gè)自定義的表單驗(yàn)證函數(shù),其函數(shù)的參數(shù)this指向elt
window.onload = () => {
// 查找一個(gè)<form>元素
var elt = document.getElementById("shipping_address");
// 注冊(cè)事件處理程序函數(shù)
// 在表單提交之前調(diào)用該函數(shù)
elt.onsubmit = () => { return validate(this); };
}
設(shè)置HTML標(biāo)簽屬性為事件處理程序
<button onclick="alert('Thank you');">點(diǎn)擊這里</button>
點(diǎn)擊按鈕會(huì)彈出一個(gè)對(duì)話框
addEventListener()
為事件綁定一個(gè)處理的函數(shù)
<script>
var b = document.getElementById("my button");
b.onclick = () => { alert("Thanks for clicking me!"); };
b.addEventListener("click", () => { alert("Thanks again!"); }, false); // 最后一個(gè)參數(shù)為是否進(jìn)行冒泡
</script>
上方會(huì)彈出兩個(gè)對(duì)話框,一個(gè)觸發(fā)了onclick事件,一個(gè)觸發(fā)了addEventListener注冊(cè)的click事件。
注冊(cè)的click處理的函數(shù),將會(huì)按照注冊(cè)的順序依次不斷的調(diào)用。(可以注冊(cè)多個(gè)處理程序)。
document.removeEventListener("mousemove", handleMouseMove, true);
移出在mousemove上注冊(cè)的事件,處理函數(shù)為handLenMouseMove,在事件上直接注冊(cè),沒有冒泡的過程。
事件處理程序的調(diào)用
事件處理程序運(yùn)行的環(huán)境
this關(guān)鍵字指的是事件的目標(biāo)。
事件處理程序的作用域
這是個(gè)坑的集散地,表單上有一個(gè)HTML事件處理程序想要引用window的location對(duì)象,必須使用window.location,不能使用location,因?yàn)樵搇ocation為表單作用域鏈上的location
事件處理程序的返回值
如果返回false則告訴瀏覽器不要執(zhí)行這個(gè)事件的默認(rèn)操作。
調(diào)用順序
通過設(shè)置對(duì)象屬性,HTML屬性注冊(cè)的處理程序一直優(yōu)先調(diào)用
使用addEventList注冊(cè)的處理程序,按照DOM的順序進(jìn)行調(diào)用
使用attachEvent()注冊(cè)的事件按照隨機(jī)的順序調(diào)用
事件傳播
當(dāng)事件目標(biāo)為window對(duì)象或其他一些單獨(dú)對(duì)象,瀏覽器會(huì)簡(jiǎn)單的調(diào)用對(duì)象上的處理程序響應(yīng)事件。
調(diào)用目標(biāo)元素上注冊(cè)的事件處理函數(shù)
在調(diào)用目標(biāo)元素上注冊(cè)的事件處理函數(shù)的時(shí)候,該事件會(huì)冒泡到DOM樹的樹跟,調(diào)用目標(biāo)的父元素的事件處理程序,即可以在共同的祖先元素上注冊(cè)一個(gè)處理程序來處理所有的事件
例如:在form元素上注冊(cè)change事件處理程序取代在表單的每個(gè)元素上注冊(cè)change事件處理程序。
原因:冒泡
注意:load事件,(load當(dāng)資源加載完成以后,將會(huì)觸發(fā)load事件,不單單指整個(gè)文檔)其會(huì)在Document對(duì)象上停止冒泡不會(huì)傳播到window對(duì)象,只有整個(gè)文檔都加載完成的時(shí)候?qū)?huì)觸發(fā)window對(duì)象的load事件
捕獲
事件的第一階段 捕獲 發(fā)生在目標(biāo)處理程序調(diào)用程序之前 addEventener()把一個(gè)布爾值作為其第三個(gè)參數(shù),如果為true那么事件處理程序被注冊(cè)為捕獲事件處理程序,會(huì)在事件傳播的第一個(gè)階段調(diào)用。事件傳播的捕獲階段像是反向的冒泡,,將會(huì)最先調(diào)用window對(duì)象的捕獲處理程序,然后將會(huì)調(diào)用document對(duì)象的捕獲處理程序,接著是body對(duì)象的捕獲處理程序,逐漸按照DOM樹往下,直到調(diào)用事件目標(biāo)的父元素的捕獲事件捕獲。
事件取消
通過調(diào)用事件對(duì)象的preventDefault()方法取消事件的默認(rèn)操作。
文檔加載事件
web應(yīng)用需要web瀏覽器通知它們文檔加載完畢和為操作準(zhǔn)備就緒的時(shí)間。
當(dāng)文檔準(zhǔn)備就緒的時(shí)候調(diào)用函數(shù)
事件冒泡和傳播
事件冒泡屬于微軟的,向上
事件傳播屬于網(wǎng)景瀏覽器(懷舊,一個(gè)時(shí)代,可惜已經(jīng)不存在了),正好相反。
后來w3c將這兩種給統(tǒng)一了,規(guī)定任何事件首先向下傳播直到遇到目標(biāo)元素,如果沒有遇到冒泡元素,將會(huì)不斷的向上冒泡進(jìn)行返回。
件被看作是 JavaScript 與網(wǎng)頁(yè)之間交互的橋梁,當(dāng)事件發(fā)生時(shí),可以通過 JavaScript 代碼(函數(shù))執(zhí)行相關(guān)的操作。例如,用戶可以通過鼠標(biāo)拖曳登錄框,改變登錄框的顯示位置;或者在閱讀文章時(shí),選中文本后自動(dòng)彈出分享、復(fù)制選項(xiàng)。本章將對(duì) DOM 中的事件進(jìn)行詳細(xì)講解。
事件可被理解為是 JavaScript 偵測(cè)到的行為,這些行為指的就是頁(yè)面的加載、鼠標(biāo)單擊頁(yè)面、鼠標(biāo)滑過某個(gè)區(qū)域、按下鍵盤等具體的動(dòng)作,它對(duì)實(shí)現(xiàn)網(wǎng)頁(yè)的交互效果起著重要的作用。在深入學(xué)習(xí)事件時(shí),需要對(duì)一些非常基本又相當(dāng)重要的概念有一定的了解。
事件處理程序指的就是 JavaScript 為響用戶行為所執(zhí)行的程序代碼。例如,用戶單擊 button 按鈕時(shí),這個(gè)行為就會(huì)被 JavaScript 中的click 事件偵測(cè)到;然后讓其自動(dòng)執(zhí)行,為 click 事件編寫的程序代碼,如在控制臺(tái)輸出“按鈕被單擊了”。
事件驅(qū)動(dòng)是指,在 Web 頁(yè)面中 JavaScript 的事件,偵測(cè)到的用戶行為(如鼠標(biāo)單擊、鼠標(biāo)移入等),并執(zhí)行相應(yīng)的事件處理程序的過程。
事件綁定指的是為某個(gè)元素對(duì)象的事件綁定事件處理程序。在 DOM 中提供了3種事件的綁定方式。下面將針對(duì)以3種事件綁定方式的語(yǔ)法以及各自的區(qū)別進(jìn)行詳細(xì)講解。
事件的行內(nèi)綁定式是通過HTML標(biāo)簽的屬性設(shè)置實(shí)現(xiàn)的,具體語(yǔ)法格式如下。
<div onclick="alert('handle click')"></div>
在上述語(yǔ)法中,div 可以是任意的HTML標(biāo)簽,如 <button>、<input>標(biāo)簽等;事件是由 on 和事件名稱組成的一個(gè) HTML 屬性,如單擊事件對(duì)應(yīng)的屬性名為 onclick;事件的處理程序指的是 JavaScript 代碼,如匿名函數(shù)等。
需要注意的是,由于開發(fā)中提倡 JavaScript 代碼與 HTML 代碼相分離。因此,不建議使用行內(nèi)綁定事件。
動(dòng)態(tài)的綁定方式很好地解決了JavaScript代碼與HTML代碼混合編寫的問題。在JavaScript代碼中,為需要事件處理的 DOM 元素對(duì)象,添加事件與事件處理程序。具體語(yǔ)法格式如下。
div.onclick = function handleClick () {
console.log('handle click');
};
在上述語(yǔ)法中,事件的處理程序一般都是匿名函數(shù)或有名的函數(shù)。在實(shí)際開發(fā)中,相對(duì)于行內(nèi)綁定來說,事件的動(dòng)態(tài)綁定的使用居多。
行內(nèi)綁定與動(dòng)態(tài)綁定除了實(shí)現(xiàn)的語(yǔ)法不同以外,本質(zhì)是相同的,都是給 DOM 元素綁定了一個(gè) onclick 屬性。
為了給同一個(gè) DOM 對(duì)象的同一個(gè)事件添加多個(gè)事件處理程序,DOM中引入了事件流的概念,可以讓DOM對(duì)象通過事件監(jiān)聽的方式實(shí)現(xiàn)事件的綁定。由于不同瀏覽器采用的事件流實(shí)現(xiàn)方式不同,事件監(jiān)聽的實(shí)現(xiàn)存在兼容性問題。通常根據(jù)瀏覽器的內(nèi)核可以劃分為兩大類,一類是早期版本的IE瀏覽器(如IE6~8),一類遵循W3C標(biāo)準(zhǔn)的瀏覽器(以下簡(jiǎn)稱標(biāo)準(zhǔn)瀏覽器)。
接下來,將根據(jù)不同類型的瀏覽器,分別介紹事件監(jiān)聽的實(shí)現(xiàn)方式。
(1)早期版本的IE瀏覽器
在早期版本的IE瀏覽器中,事件監(jiān)聽的語(yǔ)法格式如下。
DOM對(duì)象.attachEvent(type,callback);
在上述語(yǔ)法中,參數(shù) type 指的是為 DOM 對(duì)象綁定的事件類型,它是由 on 與事件名稱組成的,如 onclick。。參數(shù) callback 表示事件的處理程序。
(2)標(biāo)準(zhǔn)瀏覽器
標(biāo)準(zhǔn)瀏覽器包括IE8版本以上的IE瀏覽器(如IE9~11),新版的Firefox、Chrome等瀏覽器。具體語(yǔ)法格式如下。通過這種方式我們可以給元素注冊(cè)多個(gè)事件處理函數(shù),而 btn.onclick = fn 是賦值操作只能設(shè)置一個(gè)事件處理函數(shù)。
DOM對(duì)象.addEventListener(type, callback, [capture]);
在上述語(yǔ)法中,參數(shù) type 指的是 DOM 對(duì)象綁定的事件類型,它是由事件名稱設(shè)置的,如 click。參數(shù) callback 表示事件的處理程序。參數(shù) capture 默認(rèn)值為 false,這個(gè)屬性后面單獨(dú)介紹,一般情況我們都使用它的默認(rèn)值。
現(xiàn)在 IE 瀏覽器已經(jīng)被淘汰,所以我們不需要再去記憶 attachEvent() 的用法,但是我們需要了解過去,過去在使用這種方式注冊(cè)事件的時(shí)候需要處理瀏覽器的兼容性,下面我們演示下:
function addEventListener(element, type, listener) {
// 能力檢測(cè): 就是要看當(dāng)前的瀏覽器是否支持此標(biāo)簽對(duì)象的屬性或是方法
if (element.addEventListener) {
element.addEventListener(type, listener, false);
} else if (element.attachEvent) {
element.attachEvent('on' + type, listener);
} else {
element['on' + type] = listener;
}
}
在事件處理函數(shù)中的 this 指向當(dāng)前觸發(fā)該事件的 DOM 元素。
link.onclick = function handleLink () {
photo.src = this.href;
p.textContent = this.title;
return false;
};
但是通過行內(nèi)綁定注冊(cè)的事件,調(diào)用的函數(shù)中 this 指向的是 window。
<button onclick="handle()">按鈕</button>
<script>
function handle () {
// 此處的 this 指向 window
console.log(this);
}
</script>
行內(nèi)綁定事件 onclick="handle()" 中的 "" 雙引號(hào)內(nèi)部其實(shí)可以看做是一個(gè)匿名函數(shù),"" 雙引號(hào)內(nèi)部的這個(gè)匿名函數(shù)才是事件處理函數(shù),在事件處理函數(shù)中又調(diào)用了 handle() 方法。
<!-- 此處的 this 指向的是觸發(fā)事件的對(duì)象 button -->
<button onclick="handle(this)">按鈕</button>
<script>
function handle (btn) {
// 此處的 this 指向 window
console.log(btn);
}
</script>
綁定事件的元素可以解除綁定,例如:我們可以讓按鈕點(diǎn)擊一次之后解除事件綁定。三種綁定事件的解除事件的方式不同,下面我們分別來介紹。
行內(nèi)綁定事件和動(dòng)態(tài)綁定事件本質(zhì)上都是給 DOM 元素設(shè)置 onclick 屬性,對(duì)應(yīng)的解除綁定事件的方式都是把 onclick 屬性重新設(shè)置為 null。
當(dāng)按鈕執(zhí)行完點(diǎn)擊事件的處理程序后立即解除事件的綁定
<button onclick="handle(this)">按鈕</button>
<script>
function handle (btn) {
alert('Hello');
btn.onclick = null;
}
</script>
btn.onclick = function handle () {
this.onclick = null;
};
標(biāo)準(zhǔn)綁定事件使用 addEventListener(type, callback, [capture]); 方法,對(duì)應(yīng)的解除綁定使用的方法是 removeEventListener(type, callback, [capture]),需要注意的是,如果注冊(cè)的事件需要解除的話,使用 addEventListener() 注冊(cè)事件的時(shí)候,傳入的 callback 不能是匿名函數(shù),因?yàn)榻獬录壎ǖ臅r(shí)候還需要引用這個(gè)函數(shù)。
const div = document.querySelector('#div');
div.addEventListener('click', handle);
function handle () {
alert('hello');
this.removeEventListener('click', handle);
}
我們已經(jīng)會(huì)使用 addEventListener(type, callback, [capture]),方法給元素注冊(cè)事件,但是這個(gè)方法的第三個(gè)參數(shù)的作用我們還不清楚,下面我們就來介紹該方法的第三個(gè)參數(shù),這里我們需要先來學(xué)習(xí) DOM 中的事件流(事件模型)。
DOM (文檔對(duì)象模型)結(jié)構(gòu)是一個(gè)樹型結(jié)構(gòu),當(dāng)一個(gè)HTML元素產(chǎn)生一個(gè)事件時(shí),該事件會(huì)在元素結(jié)點(diǎn)與根節(jié)點(diǎn)之間按特定的順序傳播,路徑所經(jīng)過的節(jié)點(diǎn)都會(huì)收到該事件,這個(gè)傳播過程可稱為 DOM 事件流。
事件順序有兩種類型:事件捕捉和事件冒泡。
這是 IE 瀏覽器對(duì)事件模型的實(shí)現(xiàn),也是最容易理解的。冒泡,顧名思義,事件像個(gè)水中的氣泡一樣一直往上冒,直到頂端。
從DOM 樹型結(jié)構(gòu)上理解,就是事件由葉子節(jié)點(diǎn)沿祖先結(jié)點(diǎn)一直向上傳遞直到根節(jié)點(diǎn);從瀏覽器界面視圖 HTML 元素排列層次上理解就是事件由具有從屬關(guān)系的觸發(fā)事件的元素一直傳遞到根元素直到文檔對(duì)象。
addEventListener(type, callback, [capture]),該方法的第三個(gè)參數(shù)為 false 的時(shí)候設(shè)置觸發(fā)事件的方式為事件冒泡,該參數(shù)默認(rèn)為 false。
一般情況下,我們都會(huì)使用事件冒泡的方式注冊(cè)事件。
const outer = document.querySelector('#outer');
const inner = document.querySelector('#inner');
outer.addEventListener('click', function () {
console.log('點(diǎn)擊了 outer');
}, false);
inner.addEventListener('click', function () {
console.log('點(diǎn)擊了 inner');
}, false);
document.body.addEventListener('click', function () {
console.log('點(diǎn)擊了 body');
}, false);
document.addEventListener('click', function () {
console.log('點(diǎn)擊了 document');
}, false);
window.addEventListener('click', function () {
console.log('點(diǎn)擊了 window');
}, false);
執(zhí)行結(jié)果:
使用行內(nèi)綁定和動(dòng)態(tài)綁定事件的方式默認(rèn)使用的是事件冒泡。
Netscape 的實(shí)現(xiàn),它與冒泡型剛好相反,由 DOM 樹最頂層元素一直到觸發(fā)事件的元素。
addEventListener(type, callback, [capture]),該方法的第三個(gè)參數(shù)為 true 的時(shí)候設(shè)置觸發(fā)事件的方式為事件捕獲。
const outer = document.querySelector('#outer');
const inner = document.querySelector('#inner');
outer.addEventListener('click', function () {
console.log('點(diǎn)擊了 outer');
}, true);
inner.addEventListener('click', function () {
console.log('點(diǎn)擊了 inner');
}, true);
document.body.addEventListener('click', function () {
console.log('點(diǎn)擊了 body');
}, true);
document.addEventListener('click', function () {
console.log('點(diǎn)擊了 document');
}, true);
window.addEventListener('click', function () {
console.log('點(diǎn)擊了 window');
}, true);
執(zhí)行結(jié)果:
使用行內(nèi)綁定和動(dòng)態(tài)綁定事件的方式無法使用事件捕獲。
我們已經(jīng)對(duì)上面兩個(gè)不同的事件模型進(jìn)行了解釋和對(duì)比。DOM 標(biāo)準(zhǔn)同時(shí)支持兩種事件模型,即事件捕獲與事件冒泡,但是,事件捕獲先發(fā)生。兩種事件流都會(huì)觸發(fā) DOM 中的所有對(duì)象,從 document對(duì)象開始,也在 document 對(duì)象結(jié)束(大部分兼容標(biāo)準(zhǔn)的瀏覽器會(huì)繼續(xù)將事件是捕捉/冒泡延續(xù)到window 對(duì)象)。
如圖:首先是捕獲傳遞事件,接著是冒泡傳遞,所以,如果一個(gè)處理函數(shù)既注冊(cè)了捕獲型事件的監(jiān)聽,又注冊(cè)冒泡型事件監(jiān)聽,那么在 DOM 事件模型中它就會(huì)被調(diào)用兩次。
DOM 標(biāo)準(zhǔn)的事件模型最獨(dú)特的性質(zhì)是,文本節(jié)點(diǎn)也會(huì)觸發(fā)事件(在IE不會(huì))。
事件委托,通俗地來講,就是把一個(gè)元素的處理事件的函數(shù)委托到另一個(gè)元素。
一般來講,會(huì)把一個(gè)或者一組元素的事件委托到它的父層或者更外層元素上,真正綁定事件的是外層元素,當(dāng)事件響應(yīng)到需要綁定的元素上時(shí),會(huì)通過事件冒泡機(jī)制從而觸發(fā)它的外層元素的綁定事件上,然后在外層元素上去執(zhí)行函數(shù)。
舉個(gè)例子,比如一個(gè)宿舍的同學(xué)同時(shí)快遞到了,一種方法就是他們都傻傻地一個(gè)個(gè)去領(lǐng)取,還有一種方法就是把這件事情委托給宿舍長(zhǎng),讓一個(gè)人出去拿好所有快遞,然后再根據(jù)收件人一一分發(fā)給每個(gè)宿舍同學(xué)。
在這里,取快遞就是一個(gè)事件,每個(gè)同學(xué)指的是需要響應(yīng)事件的 DOM 元素,而出去統(tǒng)一領(lǐng)取快遞的宿舍長(zhǎng)就是代理的元素,所以真正綁定事件的是這個(gè)元素,按照收件人分發(fā)快遞的過程就是在事件執(zhí)行中,需要判斷當(dāng)前響應(yīng)的事件應(yīng)該匹配到被代理元素中的哪一個(gè)或者哪幾個(gè)。
下面我們來做一個(gè)練習(xí),為下面的每一個(gè) li 注冊(cè)點(diǎn)擊事件,當(dāng)點(diǎn)擊當(dāng)前 li 的時(shí)候打印 li 中的文本內(nèi)容。
<ul id="list">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
......
<li>item n</li>
</ul>
首先我們用傳統(tǒng)的方式來實(shí)現(xiàn),先獲取到頁(yè)面上所有的 li,然后遍歷所有的 li,給每一個(gè) li 注冊(cè)點(diǎn)擊事件,這里使用 addEventListener() 注冊(cè)事件的時(shí)候省略了第三個(gè)參數(shù),默認(rèn)為 false,事件冒泡的方式。
這樣做不好的地方有兩點(diǎn),第一:我們需要為每一個(gè) li 元素創(chuàng)建一個(gè)新的事件處理函數(shù),每次創(chuàng)建都需要銷毀時(shí)間和內(nèi)存。第二:當(dāng)點(diǎn)擊按鈕往 ul 中添加新的 li 元素的時(shí)候需要給新創(chuàng)建的 li 注冊(cè)點(diǎn)擊事件。
const lis = document.querySelectorAll('#list li');
lis.forEach(function (li) {
li.addEventListener('click', function () {
console.log(this.textContent)
});
});
下面我們使用事件委托的方式優(yōu)化上面的代碼,把點(diǎn)擊事件注冊(cè)給父元素 ul,當(dāng)點(diǎn)擊 li 的時(shí)候通過事件冒泡把點(diǎn)擊事件傳遞給父元素 ul。
const ul = document.querySelector('#list');
ul.addEventListener('click', function () {
console.log('test');
// 此處的 this 是注冊(cè)事件的元素 ul
console.log(this);
});
代碼改完之后點(diǎn)擊 li,這段代碼確實(shí)可以執(zhí)行,但是我們的目標(biāo)是打印 li 之間的內(nèi)容,而通過打印發(fā)現(xiàn)此處的 this 不是我們想要的當(dāng)前點(diǎn)擊的 li,而是注冊(cè)事件的元素 ul。所以這里需要強(qiáng)調(diào)一點(diǎn),在注冊(cè)事件的時(shí)候,事件源是注冊(cè)事件的對(duì)象。
那如何獲取當(dāng)前觸發(fā)事件的元素 li 呢?當(dāng)事件被觸發(fā)的時(shí)候事件處理函數(shù)會(huì)接收一個(gè)參數(shù),這個(gè)參數(shù)叫做事件對(duì)象,事件對(duì)象可以提供觸發(fā)事件的時(shí)候相關(guān)的數(shù)據(jù),下一小節(jié)詳細(xì)介紹,這里我們先用事件對(duì)象解決當(dāng)前的問題,事件對(duì)象中有一個(gè) target 屬性,這個(gè)屬性就是當(dāng)前觸發(fā)事件的對(duì)象。
在 IE 瀏覽器中獲取事件對(duì)象的方式不同,IE 中是通過 window.event 獲取事件對(duì)象,以前在獲取事件對(duì)象的時(shí)候還要處理瀏覽器兼容性問題,IE 瀏覽器現(xiàn)在已經(jīng)被淘汰所以瀏覽器兼容性的處理我們就不再演示。
const ul = document.querySelector('#list');
// 事件參數(shù)(對(duì)象) e
ul.addEventListener('click', function (e) {
// e.target 觸發(fā)事件的元素
console.log(e.target.textContent);
// 注冊(cè)事件的元素
console.log(this);
});
到這里這個(gè)案例就完成了,我們?cè)賮頂U(kuò)展下這個(gè)案例,如果想要點(diǎn)擊特定的 li 來觸發(fā)事件該如何實(shí)現(xiàn)?
<ul id="list">
<li>item 1</li>
<li class="cls">item 2</li>
<li class="cls">item 3</li>
......
<li>item n</li>
</ul>
如上代碼,如果想點(diǎn)擊具有特性類樣式或者特定 id 的元素觸發(fā)事件,可以通過判斷當(dāng)前點(diǎn)擊的元素 e.target 的類樣式或者 id 屬性進(jìn)行判斷。
if (e.target.className === 'cls') {
// ....
}
但是如果想像 CSS 選擇器一樣更加靈活的匹配的話,上面的判斷不夠靈活,這里可以使用 元素.matches(選擇器) 來匹配特定元素。當(dāng)元素匹配指定的選擇器返回 true。
const ul = document.querySelector('#list');
ul.addEventListener('click', function (e) {
// matches 方法,當(dāng)元素匹配指定的選擇器返回 true
if (e.target.matches('.cls')) {
console.log(e.target.textContent);
}
});
利用事件冒泡的特性,將本應(yīng)該注冊(cè)在子元素上的處理事件注冊(cè)在父元素上,這樣點(diǎn)擊子元素時(shí)發(fā)現(xiàn)其本身沒有相應(yīng)事件就到父元素上尋找作出相應(yīng)。這樣做的優(yōu)勢(shì)有:1. 減少內(nèi)存消耗,避免重復(fù)創(chuàng)建相同事件處理函數(shù),只需要把多個(gè)子元素的事件委托給父元素。2.隨時(shí)可以添加子元素,添加的子元素會(huì)自動(dòng)有相應(yīng)的處理事件。
const tbody = document.querySelector('tbody');
tbody.addEventListener('click', function (e) {
// 注冊(cè)事件的元素 tbody
// console.log(this);
// 觸發(fā)事件的元素(你點(diǎn)擊的那個(gè)元素)
// console.log(e.target)
// 判斷元素是否是指定的元素
// console.log(e.target.matches('.del'))
if (e.target.matches('.del')) {
e.target.parentNode.parentNode.remove();
}
});
每當(dāng)觸發(fā)一個(gè)事件,就會(huì)產(chǎn)生一個(gè)事件對(duì)象 event,該對(duì)象包含著所有與事件有關(guān)的信息。包括導(dǎo)致事件的元素、事件的類型以及其他與特定事件相關(guān)的信息。上一小節(jié)中我們使用事件對(duì)象獲取觸發(fā)事件的元素。
例如:鼠標(biāo)操作產(chǎn)生的 event中會(huì)包含鼠標(biāo)位置的信息;鍵盤操作產(chǎn)生的event中會(huì)包含與按下的鍵有關(guān)的信息。
所有瀏覽器都支持 event 對(duì)象,但支持方式不同,在標(biāo)準(zhǔn) DOM 中 event 對(duì)象必須作為唯一的參數(shù)傳給事件處理函數(shù),在 IE 中 event 是 window 對(duì)象的一個(gè)屬性。
成員 | 描述 | 備注 |
type | 觸發(fā)的事件名稱 | |
eventPhase | 事件流在傳播階段的位置 | |
target | 觸發(fā)事件的元素 | |
srcElement | target 的別名,老版本的 IE 中使用 | |
clientX / clientY | 基于瀏覽器的可視區(qū)域,鼠標(biāo)坐標(biāo)值 | 可配合固定定位,基于窗口定位 |
pageX / pageY | 基于整個(gè)頁(yè)面,頁(yè)面滾動(dòng)有關(guān),鼠標(biāo)在頁(yè)面的坐標(biāo)值 | 可配合絕對(duì)定位,基于頁(yè)面定位 |
key | 獲取按鍵輸入 | |
preventDefault() | 取消默認(rèn)行為 | |
stopPropagation() | 阻止事件冒泡 |
const img = document.querySelector('#img');
document.addEventListener('mousemove', function (e) {
// 鼠標(biāo)位置 - 圖片大小的一半
img.style.left = e.clientX - 96 / 2 + 'px';
img.style.top = e.clientY - 80 / 2 + 'px';
});
設(shè)置樣式,讓 body 的高度等于 1500px(垂直方向出現(xiàn)滾動(dòng)條),滾動(dòng)條下拉這時(shí)候移動(dòng)鼠標(biāo),圖片的縱向位置跟鼠標(biāo)脫離。
原因是 clientX 和 clientY 獲取的是鼠標(biāo)在當(dāng)前可視區(qū)域的位置。如果出現(xiàn)滾動(dòng)條的話可以通過 pageX 和 pageY 獲取鼠標(biāo)在當(dāng)前文檔中的位置。
const img = document.querySelector('#img');
document.addEventListener('mousemove', function (e) {
img.style.left = e.pageX - 96 / 2 + 'px';
img.style.top = e.pageY - 80 / 2 + 'px';
});
這里獲取圖片大小的時(shí)候?qū)懙氖蔷唧w值,將來圖片替換后,還需要改變這里的大小。我們可以使用 getComputedStyle() 獲取圖片的大小。
const img = document.querySelector('#img');
img.addEventListener('load', function () {
const style = window.getComputedStyle(img, null);
const imgWidth = parseInt(style.width);
const imgHeight = parseInt(style.height);
document.addEventListener('mousemove', function (e) {
img.style.left = e.pageX - imgWidth / 2 + 'px';
img.style.top = e.pageY - imgHeight / 2 + 'px';
});
});
注意:這里需要在 img 標(biāo)簽加載完畢后獲取圖片的大小,否則獲取到的圖片大小是 0,因?yàn)?load 事件代表圖片被加載,否則的話代碼從上到下執(zhí)行到這個(gè)位置,圖片還沒有被下載回來,這個(gè)時(shí)候獲取圖片的大小是 0。
#img {
width: 100px;
position: absolute;
left: 0;
top: 0;
}
.toLeft {
transform: rotateY(180deg);
}
const img = document.querySelector('#img');
let x = 0;
let y = 0;
document.addEventListener('keydown', function (e) {
switch (e.key) {
case 'ArrowLeft':
x -= 10;
img.classList.add('toLeft');
break;
case 'ArrowRight':
x += 10;
img.classList.remove('toLeft');
break;
case 'ArrowUp':
y -= 10;
break;
case 'ArrowDown':
y += 10;
break;
}
img.style.left = x + 'px';
img.style.top = y + 'px';
});
// contextmenu 鼠標(biāo)右鍵事件
document.addEventListener('contextmenu', function(e) {
// 禁止點(diǎn)擊的默認(rèn)行為,即顯示上下文菜單
e.preventDefault()
});
// 禁止選中文字事件
document.addEventListener('selectstart', function(e) {
// 禁止選中文字的默認(rèn)行為,即不能選中文字
e.preventDefault()
})
const loginBg = document.querySelector('#bg');
const loginLink = document.querySelector('#link');
const loginBox = document.querySelector('#login');
const closeBtn = document.querySelector('#closeBtn');
const loginTitle = document.querySelector('#title');
loginLink.addEventListener('click', function () {
loginBox.style.display = 'block';
loginBg.style.display = 'block';
});
closeBtn.addEventListener('click', function () {
loginBox.style.display = 'none';
loginBg.style.display = 'none';
});
// 拖動(dòng)事件的三個(gè)過程:鼠標(biāo)按下 mousedowm,鼠標(biāo)移動(dòng) mousemove,鼠標(biāo)松開 mouseup
const style = window.getComputedStyle(loginBox, null);
// 模態(tài)框跟著鼠標(biāo)走的原理
loginTitle.addEventListener('mousedown', function (e) {
const loginLeft = parseInt(style.left);
const loginTop = parseInt(style.top);
// 步驟一:當(dāng)鼠標(biāo)按下時(shí),需要立即得到鼠標(biāo)在盒子中的坐標(biāo)
var x = e.pageX - loginLeft;
var y = e.pageY - loginTop;
// 為整個(gè)頁(yè)面添加鼠標(biāo)移動(dòng)事件
document.addEventListener('mousemove', move);
function move(e) {
// 步驟二:模態(tài)框的left和top等于鼠標(biāo)在頁(yè)面的坐標(biāo)減去鼠標(biāo)在盒子內(nèi)的坐標(biāo)
// 注意:一定要加上px
login.style.left = e.pageX - x + 'px';
login.style.top = e.pageY - y + 'px';
}
// 步驟三:鼠標(biāo)松開時(shí)取消整個(gè)頁(yè)面的鼠標(biāo)移動(dòng)事件
document.addEventListener('mouseup', function (e) {
document.removeEventListener('mousemove', move);
});
});
在這之前我們已經(jīng)使用過了單擊事件、鼠標(biāo)經(jīng)過和鼠標(biāo)離開的事件,瀏覽器給我們提供的事件種類非常多,下面我們列出一些常用的事件,使用的方式都是一樣的。
描述 | 事件名稱 |
鼠標(biāo)單擊 | click |
鼠標(biāo)雙擊 | dblclick |
鼠標(biāo)移入 | mouseover |
鼠標(biāo)移出 | mouseout |
鼠標(biāo)移動(dòng) | mousemove |
獲取焦點(diǎn) | focus |
失去焦點(diǎn) | blur |
鍵盤按下 | keydown |
鍵盤彈起 | keyup |
不能識(shí)別功能鍵 ctrl、alt 等 | keypress |
文本框的輸入事件 | input |
const search = document.querySelector('#search');
function hanldeFocus(e) {
if (e.key === 's') {
search.focus();
e.preventDefault();
}
}
document.addEventListener('keydown', hanldeFocus);
search.addEventListener('focus', function () {
document.removeEventListener('keydown', hanldeFocus);
});
search.addEventListener('blur', function () {
document.addEventListener('keydown', hanldeFocus);
});
么是事件
我想你很可能聽說過事件驅(qū)動(dòng), 但是事件驅(qū)動(dòng)到底是什么?為什么說瀏覽器是事件驅(qū)動(dòng)的呢?為什么 NodeJS 也是事件驅(qū)動(dòng)的 ? 兩者是一回事么?
實(shí)際上不管是瀏覽器還是 Nodejs 都是事件驅(qū)動(dòng)的,都有自己的事件模型。在這里,我們只講解瀏覽器端的事件模型,如果對(duì) Nodejs 事件模型感興趣的,請(qǐng)期待我的 Nodejs 部分的講解。
事件驅(qū)動(dòng)通俗地來說就是什么都抽象為事件。
瀏覽器依靠事件來驅(qū)動(dòng)APP運(yùn)行下去,如果沒有了事件驅(qū)動(dòng),那么APP會(huì)直接從頭到尾運(yùn)行完,然后結(jié)束,事件驅(qū)動(dòng)是瀏覽器的基石。
本篇文章不講解事件循環(huán)的內(nèi)容,事件循環(huán)部分會(huì)在本章的其他章節(jié)講解,敬請(qǐng)期待。
一個(gè)簡(jiǎn)單的例子
其實(shí)現(xiàn)實(shí)中的紅綠燈就是一種事件,它告訴我們現(xiàn)在是紅燈狀態(tài),綠燈狀態(tài),還是黃燈狀態(tài)。 我們需要根據(jù)這個(gè)事件自己去完成一些操作,比如紅燈和黃燈我們需要等待,綠燈我們可以過馬路。
下面我們來看一個(gè)最簡(jiǎn)單的瀏覽器端的事件:
html代碼:
<button>Change color</button>
js代碼:
var btn = document.querySelector('button'); btn.onclick = function() { console.log('button clicked')}
代碼很簡(jiǎn)單,我們?cè)赽utton上注冊(cè)了一個(gè)事件,這個(gè)事件的handler是一個(gè)我們定義的匿名函數(shù)。當(dāng)用戶點(diǎn)擊了這個(gè)被注冊(cè)了事件的button的時(shí)候,這個(gè)我們定義好的匿名函數(shù)就會(huì)被執(zhí)行。
如何綁定事件
我們有三種方法可以綁定事件,分別是行內(nèi)綁定,直接賦值,用addEventListener。
這個(gè)方法非常不推薦
html代碼:
<button onclick="handleClick()">Press me</button>
然后在script標(biāo)簽內(nèi)寫:
function handleClick() { console.log('button clicked')}
和我上面舉的例子一樣:
var btn = document.querySelector('button'); btn.onclick = function() { console.log('button clicked')}
這種方法有兩個(gè)缺點(diǎn)
btn.onclick = functionA;btn.onclick = functionB;
這樣只有functionB有效,這可以通過addEventListener來解決。
因此addEventListener橫空出世,這個(gè)也是目前推薦的寫法。
舊版本的addEventListener第三個(gè)參數(shù)是bool,新版版的第三個(gè)參數(shù)是對(duì)象,這樣方便之后的擴(kuò)展,承載更多的功能, 我們來重點(diǎn)介紹一下它。
addEventListener可以給Element,Document,Window,甚至XMLHttpRequest等綁定事件,當(dāng)指定的事件發(fā)生的時(shí)候,綁定的回調(diào)函數(shù)就會(huì)被以某種機(jī)制進(jìn)行執(zhí)行,這種機(jī)制我們稍后就會(huì)講到。
語(yǔ)法:
target.addEventListener(type, listener[, options]); target.addEventListener(type, listener[, useCapture]); target.addEventListener(type, listener[, useCapture, wantsUntrusted ]); // Gecko/Mozilla only
type是你想要綁定的事件類型,常見的有click, scroll, touch, mouseover等,舊版本的第三個(gè)參數(shù)是bool,表示是否是捕獲階段,默認(rèn)是false,即默認(rèn)為冒泡階段。新版本是一個(gè)對(duì)象,其中有capture(和上面功能一樣),passive和once。 once用來執(zhí)行是否只執(zhí)行一次,passive如果被指定為true表示永遠(yuǎn)不會(huì)執(zhí)行preventDefault(),這在實(shí)現(xiàn)絲滑柔順的滾動(dòng)的效果中很重要。更多請(qǐng)參考Improving scrolling performance with passive listeners
框架中的事件
實(shí)際上,我們現(xiàn)在大多數(shù)情況都是用框架來寫代碼,因此上面的情況其實(shí)在現(xiàn)實(shí)中是非常少見的,我們更多看到的是框架封裝好的事件,比如React的合成事件,感興趣的可以看下這幾篇文章。
雖然我們很少時(shí)候會(huì)接觸到原生的事件,但是了解一下事件對(duì)象,事件機(jī)制,事件代理等還是很有必要的,因?yàn)榭蚣艿氖录到y(tǒng)至少在這方面還是一致的,這些內(nèi)容我們接下來就會(huì)講到。
事件對(duì)象
所有的事件處理函數(shù)在被瀏覽器執(zhí)行的時(shí)候都會(huì)帶上一個(gè)事件對(duì)象,舉個(gè)例子:
function handleClick(e) { console.log(e);} btn.addEventListener('click', handleClick);
這個(gè)e就是事件對(duì)象,即event object。 這個(gè)對(duì)象有一些很有用的屬性和方法,下面舉幾個(gè)常用的屬性和方法。
事件傳播
前面講到了事件默認(rèn)是綁定到冒泡階段的,如果你顯式令useCapture為true,則會(huì)綁定到捕獲階段。
事件捕獲很有意思,以至于我會(huì)經(jīng)常出事件的題目加上一點(diǎn)事件傳播的機(jī)制,讓候選人進(jìn)行回答,這很能體現(xiàn)一個(gè)人的水平。了解事件的傳播機(jī)制,對(duì)于一些特定問題有著非常大的作用。
一個(gè)Element上綁定的事件觸發(fā)了,那么其實(shí)會(huì)經(jīng)過三個(gè)階段。
從最外層即HTML標(biāo)簽開始,檢查當(dāng)前元素有沒有綁定對(duì)應(yīng)捕獲階段事件,如果有則執(zhí)行,沒有則繼續(xù)往里面?zhèn)鞑ィ@個(gè)過程遞歸執(zhí)行直到觸達(dá)觸發(fā)這個(gè)事件的元素為止。
偽代碼:
上面已經(jīng)提到了,這里省略了。
從觸發(fā)這個(gè)事件的元素開始,檢查當(dāng)前元素有沒有綁定對(duì)應(yīng)冒泡階段事件,如果有則執(zhí)行,沒有則繼續(xù)往里面?zhèn)鞑ィ@個(gè)過程遞歸執(zhí)行直到觸達(dá)HTML為止。
偽代碼:
上述的過程用圖來表示為:
如果你不希望事件繼續(xù)冒泡,可以用之前我提到的stopPropagation。
偽代碼:
事件代理
利用上面提到的事件冒泡機(jī)制,我們可以選擇做一些有趣的東西。 舉個(gè)例子:
我們有一個(gè)如下的列表,我們想在點(diǎn)擊對(duì)應(yīng)列表項(xiàng)的時(shí)候,輸出是點(diǎn)擊了哪個(gè)元素。
HTML代碼:
<ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li></ul>
JS代碼:
document.querySelector('ul').addEventListener('click', e => console.log(e.target.innerHTML))
在線地址
上面說了addEventListener會(huì)默認(rèn)綁定到冒泡階段,因此事件會(huì)從目標(biāo)階段開始,向外層冒泡,到我們綁定了事件的ul上,ul中通過事件對(duì)象的target屬性就能獲取到是哪一個(gè)元素觸發(fā)的。
“事件會(huì)從目標(biāo)階段開始”,并不是說事件沒有捕獲階段,而是我們沒有綁定捕獲階段,我描述給省略了。
我們只給外層的ul綁定了事件處理函數(shù),但是可以看到li點(diǎn)擊的時(shí)候,實(shí)際上會(huì)打印出對(duì)應(yīng)li的內(nèi)容(1,2,3或者4)。 我們無須給每一個(gè)li綁定事件處理函數(shù),不僅從代碼量還是性能上都有一定程度的提升。
這個(gè)有趣的東西,我們給了它一個(gè)好聽的名字“事件代理”。在實(shí)際業(yè)務(wù)中我們會(huì)經(jīng)常使用到這個(gè)技巧,這同時(shí)也是面試的高頻考點(diǎn)。
總結(jié)
事件其實(shí)不是瀏覽器特有的,和JS語(yǔ)言也沒有什么關(guān)系,這也是我為什么沒有將其劃分到JS部分的原因。很多地方都有事件系統(tǒng),但是各種事件模型又不太一致。
我們今天講的是瀏覽器的事件模型,瀏覽器基于事件驅(qū)動(dòng),將很多東西都抽象為事件,比如用戶交互,網(wǎng)絡(luò)請(qǐng)求,頁(yè)面加載,報(bào)錯(cuò)等,可以說事件是瀏覽器正常運(yùn)行的基石。
我們?cè)谑褂玫目蚣芏紝?duì)事件進(jìn)行了不同程度的封裝和處理,除了了解原生的事件和原理,有時(shí)候了解一下框架本身對(duì)事件的處理也是很有必要的。
當(dāng)發(fā)生一個(gè)事件的時(shí)候,瀏覽器會(huì)初始化一個(gè)事件對(duì)象,然后將這個(gè)事件對(duì)象按照一定的邏輯進(jìn)行傳播,這個(gè)邏輯就是事件傳播機(jī)制。 我們提到了事件傳播其實(shí)分為三個(gè)階段,按照時(shí)間先后順序分為捕獲階段,目標(biāo)階段和冒泡階段。開發(fā)者可以選擇監(jiān)聽不同的階段,從而達(dá)到自己想要的效果。
事件對(duì)象有很多屬性和方法,允許你在事件處理函數(shù)中進(jìn)行讀取和操作,比如讀取點(diǎn)擊的坐標(biāo)信息,阻止冒泡等。
最后我們通過一個(gè)例子,說明了如何利用冒泡機(jī)制來實(shí)現(xiàn)事件代理。
本文只是一個(gè)瀏覽器事件機(jī)制的科普文,并沒有也不會(huì)涉及到很多細(xì)節(jié)。希望這篇文章能讓你對(duì)瀏覽器時(shí)間有更深的理解,如果你對(duì)nodejs時(shí)間模型感興趣,請(qǐng)期待我的nodejs事件模型。 事件循環(huán)和事件循環(huán)也有千絲萬(wàn)縷的聯(lián)系,如果有時(shí)間,我會(huì)出一篇關(guān)于時(shí)間循環(huán)的文章。
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。