摘要:而事件分為個級別級事件處理程序,級事件處理程序和級事件處理程序。級中沒有規(guī)范事件的相關(guān)內(nèi)容,所以沒有級事件處理。
HTML依托于JavaScript來實現(xiàn)用戶與WEB網(wǎng)頁之間的動態(tài)交互,接收用戶操作并做出相應(yīng)的反饋,而事件在此間則充當(dāng)橋梁的重要角色。
日常開發(fā)中,經(jīng)常會為某個元素綁定一個事件,編寫相應(yīng)的業(yè)務(wù)邏輯,在元素被點擊時執(zhí)行,并反饋到用戶操作界面。
這個過程中,事件就像一個偵聽器,當(dāng)點擊動作發(fā)生時,才會執(zhí)行對應(yīng)的程序。這種模式可稱之為觀察員模式。
接下來就講講DOM事件相關(guān)知識。
何為事件事件就是用戶或瀏覽器自身執(zhí)行的某種動作
常用的DOM事件有click/mouseover/mouseout/keyup/keydown等。
事件流事件流描述的是從頁面中接收事件的順序
HTML描述的是一個DOM文檔結(jié)構(gòu),而事件流所描述的是DOM文檔節(jié)點接收事件順序。
而事件流有兩種事件模式,捕獲/冒泡,兩者所描述的事件傳遞順序?qū)α⑾喾础?/p> 事件模式:捕獲與冒泡
事件冒泡:事件開始時由最具體的元素(文檔中嵌套層次最深的那個節(jié)點)接收,然后逐級向上傳播到較為不具體的節(jié)點(文檔)
規(guī)范要求事件冒泡到document對象,而瀏覽器則會將事件一直冒泡到window對象。
所有瀏覽器都支持事件冒泡(包括IE9以下)。
事件捕獲:(與事件冒泡相反)事件捕獲的思想是不太具體的節(jié)點應(yīng)該更早接收到事件,而最具體的節(jié)點應(yīng)該最后接收到事件
與冒泡一樣,雖然規(guī)定事件應(yīng)該從document對象開始傳播,但瀏覽器普遍都是從window對象開始捕獲。
IE9以下不支持事件捕獲
DOM 事件流"DOM2級事件"規(guī)定事件流包括三個階段,順序進(jìn)行
事件捕獲階段
處于目標(biāo)階段
事件冒泡階段
TIPS: 實際的目標(biāo)元素在捕獲階段不會接收到事件,在處于目標(biāo)階段時接收事件發(fā)生處理,并被看成是冒泡階段的一部分。
盡管"DOM2級事件"規(guī)范明確要求捕獲階段不會涉及事件目標(biāo),但瀏覽器會在捕獲階段觸發(fā)事件對象上的事件。
?事件處理程序響應(yīng)某個事件的函數(shù)方法,我們稱之為事件處理程序(或事件偵聽器)
window.onclick = function() { //... } // 這里的function(){}就是事件處理程序HTML事件處理程序
HTML中元素支持的事件,可以使用一個同名的HTML特性來指定,而這個特性的值就是js能執(zhí)行的代碼或表達(dá)式。寫法上可以看出類似HTML中id/type/class等屬性的寫法,都是on+"..."
缺點:HTML是結(jié)構(gòu)層(顯示層),而JavaScript是行為層(業(yè)務(wù)層)。在顯示層上去編寫業(yè)務(wù)邏輯代碼處理,會使得HTML與JavaScript代碼耦合過于緊密,不好維護(hù)。
DOM級別一共可以分為四個級別:DOM0級、DOM1級、DOM2級和DOM3級。
而DOM事件分為3個級別:DOM 0級事件處理程序,DOM 2級事件處理程序和DOM 3級事件處理程序。DOM 1級中沒有規(guī)范事件的相關(guān)內(nèi)容,所以沒有DOM 1級事件處理。
DOM0 級事件處理程序每個元素(HTML元素)都有自己的事件處理程序?qū)傩?,屬性名通常以on開頭,例如onclick/onmouseover。為這個屬性的值設(shè)置一個函數(shù),就可以指定事件處理程序。而將其屬性值賦值為null,則完成解綁。(同個元素?zé)o法綁定多個同名事件)
var myBtn = document.getElementById("myBtn"); // 為myBtn綁定事件處理程序, 只能綁定一個 myBtn.onclick = function() { alert("Hello world!"); } // 解綁 myBtn.onclick = null;DOM2 級事件處理程序
"DOM2級事件"定義了兩個方法,addEventListener()/removeEventListener(),用于為元素綁定和解綁事件。
(可綁定多個事件,區(qū)別于DOM0級/HTML僅能綁定一個)。
el.addEventListener(eventName, callBack, useCapture)
eventName: 事件名稱
callBack: 回調(diào)函數(shù),當(dāng)事件觸發(fā)時,函數(shù)會傳入一個參數(shù)event,為當(dāng)前的事件對象
useCapture: 默認(rèn)是false,代表事件句柄在冒泡階段執(zhí)行, true則代表在捕獲階段執(zhí)行
var myBtn = document.getElementById("myBtn"); var handleClick = function() { alert("Hello world!"); } // 綁定事件處理程序 myBtn.addEventListener("click", handleClick, false); // 解綁 myBtn.removeEventListener("click", handleClick);
TIPS:DOM2級事件處理程序,解綁時function必須與傳入addEventListener相同
// 綁定 myBtn.addEventListener("click", function() { // 匿名函數(shù) }); // 解綁 myBtn.removeEventListener("click",function() { // 匿名函數(shù) }); // add/remove 分別綁定了兩個匿名函數(shù)(函數(shù)為引用類型),所以兩個函數(shù)并不相同,所以無法成功解綁
TIPS:綁定多個事件處理程序時,執(zhí)行順序按綁定順序執(zhí)行
myBtn.addEventListener("click", function() { // step1... }) myBtn.addEventListener("click", function() { // step2... }) // 執(zhí)行順序:step1 -> step2
瀏覽器支持情況:IE9以下不支持DOM2級事件處理程序
IE 事件處理程序IE9以下不支持DOM2級事件,但I(xiàn)E提供了與DOM2級事件類似的兩個方法,attachEvent()/detachEvent,IE9以下不支持事件捕獲,所以attachEvent僅支持冒泡階段觸發(fā),只接收兩個參數(shù)(eventName, function)。
// 綁定 myBtn.attachEvent("onclick", handleClick); // 解綁 myBtn.detachEvent("onclick", handleClick);
TIPS:
解綁時function必須與傳入attachEvent相同,這點與DOM2級事件相同
與DOM0級的區(qū)別,DOM0級事件處理在元素的作用域運行,而attachEvent事件處理在全局,this指向window
綁定多個事件處理程序時,執(zhí)行順序按綁定順序逆反執(zhí)行(與DOM2級相反)
myBtn.attachEvent("click", function() { // step1... }) myBtn.attachEvent("click", function() { // step2... }) // 執(zhí)行順序:step2 -> step1Event 事件對象 常見應(yīng)用
阻止默認(rèn)事件
阻止事件流發(fā)生傳遞(冒泡/捕獲)
阻止剩余事件處理函數(shù)的執(zhí)行,并阻止當(dāng)前事件在事件流上傳遞
當(dāng)前綁定事件的元素
當(dāng)前觸發(fā)事件的元素
同個元素綁定多個同名事件時,stopImmediatePropagation不僅阻止了冒泡,而且會阻止后續(xù)事件的執(zhí)行,可以理解為加強(qiáng)版的stopPropagation
myBtn.addEventListener("click", function(event) { // step1; event.stopImmediatePropagation(); }) myBtn.addEventListener("click", function(event) { // step2; // 我被stopImmediatePropagation阻止掉了?。?! })
事件處理程序內(nèi)部,this等于currentTarget(當(dāng)前綁定事件的元素),而target(當(dāng)前觸發(fā)事件的元素)
// currentTarget == target myBtn.addEventListener("click", function(event) { event.target == event.currentTarget; // true -> myBtn }) // currentTarget != target 捕獲/冒泡 document.body.addEventListener("click", function(event){ event.target == event.currentTarget; // false // event.target -> myBtn // event.currentTarget -> body })內(nèi)存與性能
WEB網(wǎng)頁是運行在瀏覽器客戶端的,而計算機(jī)分配給瀏覽器的內(nèi)存及CPU占用是有限制的。雖說瀏覽器引擎不斷地發(fā)展優(yōu)化,但是內(nèi)存占用多了, 性能不免會損耗。
為元素指定事件綁定程序,事實上是賦值了一個函數(shù)方法,而函數(shù)在javaScript中是一種引用類型的數(shù)據(jù)格式,既然是數(shù)據(jù)那就需要用到內(nèi)存儲存。函數(shù)創(chuàng)建多了,消耗掉內(nèi)存。
為元素指定事件綁定程序,首先需要對DOM進(jìn)行查詢,找出要綁定事件的元素。而這也會造成DOM元素的訪問次數(shù)增加。DOM的操作一直是網(wǎng)頁性能的一個優(yōu)化點。
了解完事件綁定帶來內(nèi)存跟性能的原理,我們來看一個例子,例如我們有一個ul>li的列表,要監(jiān)聽每一個li的點擊事件,并觸發(fā)事件處理程序。
多帶帶綁定的話,10個li就要對DOM元素查詢10次,創(chuàng)建的匿名函數(shù)就有10個(當(dāng)然可以共同創(chuàng)建同個函數(shù)引用),如果還有20個,30個,100個,那么這種為每個li元素多帶帶綁定事件的方法,絕對不是最優(yōu)解。
這就引出下面的優(yōu)化方案:"事件委托"。
事件委托(事件代理)對"事件處理程序綁定過多"的問題,最好的解決方案就是"事件委托"。它的原理是利用了事件流的"冒泡"機(jī)制,事件目標(biāo)元素會把事件向上層傳遞,直到document(瀏覽器會傳到window),所以父級節(jié)點是可以接收子節(jié)點的事件傳遞。
以剛剛ul>li的例子,li有很多個, 但它們有一個共同的父節(jié)點ul。li的點擊事件會冒泡到ul,因此我們可以在ul上綁定一個事件處理程序,處理所有li的點擊事件,然后通過event.target可以確定觸發(fā)事件的元素。
var ulParent = document.getElementById("parent"); ulParent.addEventListener("click", function(event) { var taget = event.target; })
通過"事件委托"減少了DOM元素的查詢,以及多個函數(shù)的內(nèi)存占用,而且還有一個好處,當(dāng)我們的li是動態(tài)的,增加和移除時,都無需再做綁定和解綁事件操作,因為它都會冒泡到父級節(jié)點。
移除多余的事件綁定文檔中移除了綁定了事件的DOM元素,如innerHTML/removeChild()/replaceChild()等可以對DOM進(jìn)行替換,而移除的DOM元素原先所綁定的事件處理程序,并不能有效被瀏覽器垃圾回收,所以占用一直存在。
所以建議在移除某個DOM元素時,如果其綁定了事件處理程序,需手動解除綁定,釋放內(nèi)存。
自定義事件除了為元素綁定支持的事件以外,我們還可以通過Event/CustomEvent來創(chuàng)建開發(fā)者自定義事件。
兩者不同的是CustomEvent可傳遞一個Object對象來傳輸數(shù)據(jù)。
// Event var eve = new Event("custome"); // CustomeEvent 可傳參數(shù) var eve = new CustomeEvent("custome", { detail: { name: "KenTsang", age: 28 } }); // 為DOM元素添加事件監(jiān)聽 ele.addEventListener("custome", function(event) { console.log(event.detail); }) // 觸發(fā)ele綁定的自定義事件 ele.dispatch(eve);
事件這塊還剩下一部分知識點,后續(xù)文章會再就模擬事件這塊知識點進(jìn)行拆分詳解。
天冷了,更文不易,望大家多多點贊。
《JavaScript高級程序設(shè)計》
作者:以樂之名本文原創(chuàng),有不當(dāng)?shù)牡胤綒g迎指出。轉(zhuǎn)載請指明出處。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/99880.html
摘要:由于計算機(jī)的國際化,組織的標(biāo)準(zhǔn)牽涉到很多其他國家,因此組織決定改名表明其國際性。規(guī)范由萬維網(wǎng)聯(lián)盟制定。級標(biāo)準(zhǔn)級標(biāo)準(zhǔn)是不存在的,級一般指的是最初支持的。 這篇筆記的內(nèi)容對應(yīng)的是《JavaScript高級程序設(shè)計(第三版)》中的第一章。 1.ECMA 和 ECMA-262 ECMA 是歐洲計算機(jī)制造商協(xié)會的縮寫,全程是 European Computer Manufacturers Ass...
摘要:解耦優(yōu)勢代碼復(fù)用,單元測試。常用比較誤區(qū)可同時判斷,可用來判斷對象屬性是否存在。使用作判斷無法進(jìn)行充分的類型檢查。文件中應(yīng)用常量參考文檔高級程序設(shè)計作者以樂之名本文原創(chuàng),有不當(dāng)?shù)牡胤綒g迎指出。 showImg(https://segmentfault.com/img/bVburXw?w=500&h=400); 編寫可維護(hù)性代碼 可維護(hù)的代碼遵循原則: 可理解性 (方便他人理解) 直觀...
摘要:項目中我們可通過設(shè)置采集率,或?qū)σ?guī)定時間內(nèi)數(shù)據(jù)匯總再上報,減少請求數(shù)量,從而緩解服務(wù)端壓力。借鑒別人的一個例子只采集上報錯誤參考文檔高級程序設(shè)計如何優(yōu)雅處理前端異常作者以樂之名本文原創(chuàng),有不當(dāng)?shù)牡胤綒g迎指出。 showImg(https://segmentfault.com/img/bVbnuud?w=640&h=640); 錯誤類型 即時運行錯誤 (代碼錯誤) 資源加載錯誤 常見...
摘要:作用域鏈查找作用域鏈的查找是逐層向上查找。而全局變量和閉包則會與之相反,繼續(xù)保存,所以使用用后需手動標(biāo)記清除,以免造成內(nèi)存泄漏。獲取元素的屬性獲取元素的屬性等參考文檔高級程序設(shè)計作者以樂之名本文原創(chuàng),有不當(dāng)?shù)牡胤綒g迎指出。 showImg(https://segmentfault.com/img/bVburXV?w=500&h=399); 作用域鏈查找 作用域鏈的查找是逐層向上查找。查...
摘要:文件內(nèi)部使用使用到的代碼引入外部文件外部代碼的地址標(biāo)簽的位置一般情況下,標(biāo)簽的位置放在標(biāo)簽中引入代碼頁面結(jié)構(gòu)對于需要引入很多的中間,如果把放在頭部,無疑會導(dǎo)致瀏覽器呈現(xiàn)頁面出現(xiàn)延遲,就是導(dǎo)致頁面出現(xiàn)空白。頁面結(jié)構(gòu)引入代碼 這篇筆記的內(nèi)容對應(yīng)的是《JavaScript高級程序設(shè)計(第三版)》中的第二章。 1.使用方式 在HTML中使用 JavaScript 的方式有兩種,第一種就是直接內(nèi)...
閱讀 3396·2023-04-25 14:07
閱讀 3466·2021-09-28 09:35
閱讀 2097·2019-08-30 15:55
閱讀 1412·2019-08-30 13:48
閱讀 2505·2019-08-30 13:16
閱讀 3206·2019-08-30 12:54
閱讀 3241·2019-08-30 11:19
閱讀 1881·2019-08-29 17:17