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 亚洲第一页视频,www.一区二区三区,浪货一天不做就难受呀

          整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          js的事件處理

          件處理

          客戶端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è)事件處理程序

          1. 給事件目標(biāo)對(duì)象或文檔元素設(shè)置屬性
          2. 將事件處理程序傳遞給對(duì)象或文檔元素的一個(gè)方法

          設(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); };

          }

          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8

          設(shè)置HTML標(biāo)簽屬性為事件處理程序

          <button onclick="alert('Thank you');">點(diǎn)擊這里</button>

          • 1

          點(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>

          • 1
          • 2
          • 3
          • 4
          • 5

          上方會(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);

          • 1

          移出在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)


          事件驅(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)綁定方式


          事件的行內(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)綁定方式


          動(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 屬性。

          標(biāo)準(zhǔn)的綁定事件方式


          為了給同一個(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

          在事件處理函數(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。

          • 解除行內(nèi)綁定的事件

          當(dāng)按鈕執(zhí)行完點(diǎn)擊事件的處理程序后立即解除事件的綁定

          <button onclick="handle(this)">按鈕</button>
          <script>
            function handle (btn) {
              alert('Hello');
              btn.onclick = null;
            }
          </script>
          • 解除動(dòng)態(tài)綁定的事件
          btn.onclick = function handle () {
          	this.onclick = null;
          };


          • 解除標(biāo)準(zhǔn)綁定事件的方式


          標(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 事件流。


          事件順序有兩種類型:事件捕捉和事件冒泡。


          事件冒泡(Event Bubbling)


          這是 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)使用的是事件冒泡。


          事件捕獲(Event Capturing)


          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)綁定事件的方式無法使用事件捕獲。


          DOM標(biāo)準(zhǔn)的事件模型


          我們已經(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)的處理事件。

          案例:購(gòu)物車刪除


          • 需求:
            • 使用事件委托優(yōu)化移除購(gòu)物車數(shù)據(jù)的功能
          • 代碼:
          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();
            }
          });


          事件對(duì)象


          每當(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è)屬性。


          • 事件對(duì)象提供的常用成員


          成員

          描述

          備注

          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()

          阻止事件冒泡



          案例:跟著鼠標(biāo)飛的圖片


          • 需求:
            • 當(dāng)鼠標(biāo)移動(dòng)的時(shí)候讓圖片跟著鼠標(biāo)走
          • 代碼:
            • 因?yàn)橐淖儓D片位置,所以讓圖片脫離文檔流
            • 注冊(cè) mousemove 事件
            • 改變圖片的坐標(biāo):鼠標(biāo)坐標(biāo) - 圖片大小的一半,讓鼠標(biāo)在圖片的中央位置
          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。

          案例:鍵盤控制圖片移動(dòng)


          • 需求:
            • 按方向鍵盤控制頁(yè)面上的圖片往相應(yīng)的方向移動(dòng)。
          • 代碼:
            • 讓圖片脫離文檔流,翻轉(zhuǎn) 180 的類樣式,可以左右移動(dòng)
            • 監(jiān)聽鍵盤按下的事件
            • 根據(jù)按下的方向鍵,控制圖片的坐標(biāo)改變 10 像素
          #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()
          })

          案例:拖拽登錄框

          • 需求:
            • 點(diǎn)擊彈出登錄框,顯示登錄框和遮罩層
            • 鼠標(biāo)放到登錄框的頭部,顯示可移動(dòng)的鼠標(biāo)樣式
            • 單按下鼠標(biāo)可以拖動(dòng)登錄看的位置,鼠標(biāo)彈起移除拖動(dòng)的功能
          • 代碼:
            • 當(dāng)鼠標(biāo)按下彈出登陸框的a標(biāo)簽的時(shí)候,彈出兩個(gè)層來
            • 單擊關(guān)閉按鈕的時(shí)候,隱藏這兩個(gè)層
            • 當(dāng)鼠標(biāo)按下title這個(gè)盒子的時(shí)候,就能夠獲取鼠標(biāo)在盒子中的坐標(biāo)位置
            • 鼠標(biāo)在文檔中移動(dòng)的時(shí)候,時(shí)時(shí)的獲取坐標(biāo),減去在盒子中的坐標(biāo),將這個(gè)值賦值給login的left和top
            • 鼠標(biāo)離開的時(shí)候,在清空事件處理程序,不要再去觸發(fā)移動(dòng)的事件中的事件處理程序了
          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


          案例:模擬 jd 搜索文本框,按 s 獲取焦點(diǎn)


          • 需求:
            • 在文檔中按 s 讓文本框獲得焦點(diǎn),注冊(cè)鍵盤事件判斷是否按 s 鍵
            • 文本框獲得焦點(diǎn)移除鍵盤事件
            • 文本框失去焦點(diǎn)注冊(cè)鍵盤事件
          • 代碼:
            • 元素.focus() 可以讓元素獲得焦點(diǎn),同時(shí)觸發(fā) focus 事件
          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);
          });

          案例:模擬輸入快遞單號(hào)文本框

          • 需求:
            • 文本框輸入過程中,上面顯示放大的輸入內(nèi)容
          • 代碼:


          作業(yè)

          用鍵盤敲鼓的游戲


          許愿墻-拖拽

          么是事件

          我想你很可能聽說過事件驅(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)通俗地來說就是什么都抽象為事件。

          • 一次點(diǎn)擊是一個(gè)事件
          • 鍵盤按下是一個(gè)事件
          • 一個(gè)網(wǎng)絡(luò)請(qǐng)求成功是一個(gè)事件
          • 頁(yè)面加載是一個(gè)事件
          • 頁(yè)面報(bào)錯(cuò)是一個(gè)事件
          • ...

          瀏覽器依靠事件來驅(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。

          • 內(nèi)聯(lián)

          這個(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)

          1. 不能添加多個(gè)同類型的handler
          btn.onclick = functionA;btn.onclick = functionB;

          這樣只有functionB有效,這可以通過addEventListener來解決。

          1. 不能控制在哪個(gè)階段來執(zhí)行,這個(gè)會(huì)在后面將事件捕獲/冒泡的時(shí)候講到。這個(gè)同樣可以通過addEventListener來解決。

          因此addEventListener橫空出世,這個(gè)也是目前推薦的寫法。

          • addEventListener

          舊版本的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的合成事件,感興趣的可以看下這幾篇文章。

          • React SyntheticEvent
          • Vue和React的優(yōu)點(diǎn)分別是什么??jī)烧叩淖詈诵牟町悓?duì)比是什么?

          雖然我們很少時(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è)常用的屬性和方法。

          • 屬性 targetx, y等位置信息timeStampeventPhase ...
          • 方法 preventDefault 用于阻止瀏覽器的默認(rèn)行為,比如a標(biāo)簽會(huì)默認(rèn)進(jìn)行跳轉(zhuǎn),form會(huì)默認(rèn)校驗(yàn)并發(fā)送請(qǐng)求到action指定的地址等stopPropagation 用于阻止事件的繼續(xù)冒泡行為,后面講事件傳播的時(shí)候會(huì)提到。 ...

          事件傳播

          前面講到了事件默認(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è)階段。

          • 第一個(gè)階段 - 捕獲階段

          從最外層即HTML標(biāo)簽開始,檢查當(dāng)前元素有沒有綁定對(duì)應(yīng)捕獲階段事件,如果有則執(zhí)行,沒有則繼續(xù)往里面?zhèn)鞑ィ@個(gè)過程遞歸執(zhí)行直到觸達(dá)觸發(fā)這個(gè)事件的元素為止。

          偽代碼:

          • 第二個(gè)階段 - 目標(biāo)階段

          上面已經(jīng)提到了,這里省略了。

          • 第三個(gè)階段 - 冒泡階段

          從觸發(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)的文章。


          主站蜘蛛池模板: 久久久无码精品国产一区| 亚洲免费一区二区| 乱子伦一区二区三区| 无码aⅴ精品一区二区三区| 亚洲一区二区三区久久| 国产美女视频一区| 内射白浆一区二区在线观看| 交换国产精品视频一区| 精品一区二区三区自拍图片区| 人妻无码一区二区视频| 69久久精品无码一区二区| 色窝窝无码一区二区三区成人网站 | 精品一区二区三区无码免费视频 | 国模吧一区二区三区精品视频| 精品一区二区三区四区在线播放 | 国产激情一区二区三区 | 国产怡春院无码一区二区 | 国产成人久久精品麻豆一区| 国产精品一区二区久久乐下载| 亚洲日韩国产一区二区三区在线| 久久精品国产第一区二区三区| 中文字幕日韩一区| 日韩在线不卡免费视频一区| 乱子伦一区二区三区| 亚洲一区在线免费观看| 午夜影院一区二区| 精品国产一区二区三区在线| 蜜臀AV无码一区二区三区| 成人精品视频一区二区三区尤物| 日韩美女视频一区| 精品不卡一区中文字幕| 亚洲一区二区三区无码影院| 国产福利一区二区在线视频 | 中文字幕精品一区二区2021年| 中文字幕一区二区三区久久网站| 中文字幕日韩一区| 日本一区二区三区在线观看视频| 色综合视频一区二区三区| 国产激情一区二区三区成人91| 亚洲日韩精品一区二区三区无码| 无码人妻AⅤ一区二区三区 |