理解瀏覽器事件模型 understandEventModel.html 代碼: understandEventModel.js: jQuery 事件模型 jQueryEventModel.html: jQueryEventModel.js: ...
理解瀏覽器事件模型
understandEventModel.html 代碼:
<!DOCTYPE HTML> <html> <head> <title>Understand Event Model</title> <meta charset="UTF-8" /> <link rel="stylesheet" href="../css/main.css"/> <style> img { display: block; margin: auto; } </style> </head> <body> <img id="example" src="images/example.jpg" alt="A bolt of lightning" onclick="console.log('At ' + formatDate(new Date()) + ' BOOM!');" /> <p>The output is printed on the console</p> <!-- 在 onsubmit 中調用返回 false 的方法不能阻止預設動作 --> <!-- <form id="myForm" action="www.cnblogs.com" onsubmit="doSubmit()"> --> <form id="myForm" action="www.cnblogs.com" onsubmit="return false"> <div> <label for="name">Name:</label> <input id="name" name="name" type="text" /> </div> <div> <button type="submit">Click me!</button> </div> </form> <form id="myForm2" action="www.cnblogs.com" onsubmit="prevent()"> <div> <label for="name2">Name:</label> <input id="name2" name="name2" type="text" /> </div> <div> <button type="submit">Click me2!</button> </div> </form> <div id="dom2"> <button id="multipleButton" type="button">multiple events</button> </div> <div id="firstLevel"> <div id='secondLevel'> <input id="testButton" type="button" value="捕獲和冒泡" /> </div> </div> <script src="js/understandEventModel.js"></script> </body> </html>
understandEventModel.js:
/* DOM Level 用來表示實現 W3C DOM 規範的級別要求 DOM Level 0 Event Model,在這個模型背後,事件處理器通過把函數賦值給元素的屬性來聲明實現 */ function formatDate(date) { return (date.getHours() < 10 ? '0' : '') + date.getHours() + ':' + (date.getMinutes() < 10 ? '0' : '') + date.getMinutes() + ':' + (date.getSeconds() < 10 ? '0' : '') + date.getSeconds() + '.' + (date.getMilliseconds() < 10 ? '00' : (date.getMilliseconds() < 100 ? '0' : '')) + date.getMilliseconds(); } /* id 為 example 的圖片在 HTML 代碼中直接在標簽屬性 onclick 中聲明瞭一個 click 事件,這個屬性值會被使用創建一個 匿名函數體,類似下麵的代碼,直接在標簽屬性中聲明事件不是推薦的做法 */ document.getElementById('example').onmouseover = function(event) { console.log(this); console.log('At ' + formatDate(new Date()) + ' Crackle!'); //當觸發事件處理器時,在所有相容標準的瀏覽器里,一個名為 Event 的對象會作為第一參數傳遞給處理器,只有 IE9 以上, //Firefox,Chrome, Safari 和 Opera 支持, IE8 之前的瀏覽器可以通過自己的方式把 Event 對象傳給全局的 event 屬性 event = event || window.event; //獲取目標元素的引用,也就是觸發事件的元素,必須通過相容標準瀏覽器的 target 屬性獲取,但是舊的 IE 使用 //的是 srcElement var target = event.target || event.srcElement; }; /* 事件冒泡 目標元素有機會處理事件之後,瀏覽器的事件處理機制會檢查元素的父元素是否包含此事件類型的處理器,如果有也會調用,再 依次檢查爺爺元素,直到 DOM 樹的頂部 */ var elements = document.getElementsByTagName('*'); //得到頁面上的所有元素 for(var i = 0; i < elements.length; i++){ if(elements[i].id != 'example'){ (function(current){ current.onclick = function(event){ event = event || window.event; var target = event.target || event.srcElement; console.log('At ' + formatDate(new Date()) + ' For ' + current.tagName + '#' + current.id + ' target is ' + target.tagName + '#' + target.id); } })(elements[i]); } } /* 要阻止事件的傳播,在現代瀏覽器中可以調用 Event 實例的 stopPropagation() 方法 在舊的 IE 瀏覽器中可以設置 Event 實例的 cancelBubble 屬性為 true 實現,現在很多新的瀏覽器也提供了該屬性,儘管 這不是 W3C 標準(我覺得這個實踐中還是不用的好,知道作用就好) 有些事件是預設動作,比如 form 的 submit,要取消這些語義動作,在新的瀏覽器調用 Event 實例的 preventDefault() 方法。舊的 IE 中沒有此方法,設置 returnValue 屬性的值為 false;另一種方法是處理器代碼返回 false <form name="myForm" onsubmit="return false;" ... > */ /* 被頁面的 onsubmit 屬性引用,但沒有成功阻止預設的 submit 動作 */ function doSubmit(){ return false; } /* 阻止了預設的 submit 動作 */ function prevent(){ event = event || window.event; event.preventDefault(); } /* DOM Level 2 event model DOM Level 0 event model 的缺點是每個元素每次只能註冊一種特定類型的事件處理器,主要是因為屬性用來存儲 事件處理器函數的引用 DOM Level 2 event model 就是為瞭解決上面的問題而建立的。這裡的事件處理器通過標準的元素方法而不是元素屬性賦值 來實現,每個 DOM 元素都實現了一個 addEventListener() 方法。 addEventListener(eventType, listener, useCapture) eventType: 要處理的事件類型,DOM 0 中使用的事件,比如 click listener:表示元素事件處理器的函數 useCapture:布爾值 */ var multipleButton = document.getElementById('multipleButton'); /* 這個 multipleButton 按鈕在下麵代碼增加事件處理器之前,上面的代碼中增加了 click 事件是處理函數,從結果可以看出, 這個處理函數有被保留,而且是作為第一個觸發 event.stopPropagation() 隨便寫在哪個處理函數中效果都一樣,不會事件冒泡到父元素 */ multipleButton.addEventListener('click', function(event){ console.log('At ' + formatDate(new Date()) + ' BOOM once!'); //event.stopPropagation(); }); multipleButton.addEventListener('click', function(event){ console.log('At ' + formatDate(new Date()) + ' BOOM twice!'); //event.stopPropagation(); }); multipleButton.addEventListener('click', function(event){ console.log('At ' + formatDate(new Date()) + ' BOOM thrice!'); event.stopPropagation(); }); /* DOM Level 2 event model 中的事件傳播 事件首先從 DOM 樹根結點向目標元素傳播,然後從目標元素向 DOM 根節點冒泡傳播。前面的階段成為捕獲階段,後者 是冒泡階段 addEventListener(eventType, listener, useCapture) 方法中的 useCapture 是用來標記函數作為何種處理器使用的,false 為 冒泡階段處理器,true 為捕獲階段處理器,預設為 false */ /*function eventLog(curElement, event){ event = event || window.event; console.log('At ' + formatDate(new Date()) + ' Capture for ' + curElement.tagName + '#' + curElement.id ) + ' target is ' + event.target.tagName + '#' + event.target.id; }*/ var firstLevel = document.getElementById('firstLevel'); var secondLevel = document.getElementById('secondLevel'); var testButton = document.getElementById('testButton'); firstLevel.onclick = function(){}; secondLevel.onclick = function(){}; testButton.onclick = function(){}; //捕獲階段運行 firstLevel.addEventListener('click', function(event){ console.log('At ' + formatDate(new Date()) + ' Capture for ' + this.tagName + '#' + this.id ) + ' target is ' + event.target.tagName + '#' + event.target.id; }, true); firstLevel.addEventListener('click', function(event){ console.log('At ' + formatDate(new Date()) + ' Bubble for ' + this.tagName + '#' + this.id ) + ' target is ' + event.target.tagName + '#' + event.target.id; }, false); secondLevel.addEventListener('click', function(event){ console.log('At ' + formatDate(new Date()) + ' Capture for ' + this.tagName + '#' + this.id ) + ' target is ' + event.target.tagName + '#' + event.target.id; }, true); secondLevel.addEventListener('click', function(event){ console.log('At ' + formatDate(new Date()) + ' Bubble for ' + this.tagName + '#' + this.id ) + ' target is ' + event.target.tagName + '#' + event.target.id; }, false); testButton.addEventListener('click', function(event){ console.log('At ' + formatDate(new Date()) + ' Capture for ' + this.tagName + '#' + this.id ) + ' target is ' + event.target.tagName + '#' + event.target.id; }, true); testButton.addEventListener('click', function(event){ console.log('At ' + formatDate(new Date()) + ' Bubble for ' + this.tagName + '#' + this.id ) + ' target is ' + event.target.tagName + '#' + event.target.id; }, false);
jQuery 事件模型
jQueryEventModel.html:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Events in jQuery Example - jQuery in Action, 3rd edition</title> <link rel="stylesheet" href="../css/main.css"/> <style> img { display: block; margin: auto; } </style> </head> <body> <img id="example" src="images/example.jpg" alt="A bolt of lightning"/> <p> <button id="btn1">Click me!</button> <button id="btn2">Don't click me!</button> </p> <ul id="myList"></ul> <p> <button id="btn">Does nothing</button> <button id="btn-attach">Attach handler</button> <button id="btn-remove">Remove handler</button> </p> <p>The output is printed on the console</p> <script src="../js/jquery-3.2.1.js"></script> <script src="js/jQueryEventModel.js"></script> </body> </html>
jQueryEventModel.js:
/* jQuery Event Model 1. 為創建事件處理器提供統一方法 2. 允許每個元素、每個事件類型註冊多個方法 3. 適用標準事件名稱,如 click 或 mouseover 4. 傳遞 Event 實例作為第一個參數 5. 規範事件實例中最常用的屬性 6. 為事件取消和阻止操作提供統一的方法 除了不支持捕獲階段,jQuery 事件模型幾乎完美支持 DOM Level 2 Event Model */ function formatDate(date) { return (date.getHours() < 10 ? '0' : '') + date.getHours() + ':' + (date.getMinutes() < 10 ? '0' : '') + date.getMinutes() + ':' + (date.getSeconds() < 10 ? '0' : '') + date.getSeconds() + '.' + (date.getMilliseconds() < 10 ? '00' : (date.getMilliseconds() < 100 ? '0' : '')) + date.getMilliseconds(); } /* on(eventType[, selector][, data], handler) on(eventHash[, selector][, data]) 為選擇的元素的一個或多個事件添加一個或多個事件處理器 eventType(String):事件類型的名稱,多個事件類型可以用空格分隔 selector(String):過濾器用來過濾選中元素的子元素,這些子元素觸發事件 data(Any):傳遞給 Event 實例的數據,賦值給 data 屬性 handler(Function):事件處理器的函數,冒泡階段的當前元素作為函數上下文,false 值表示函數 return false eventHash(Object):單個調用中為多個事件類型建立處理器的對象。屬性名區分事件類型,屬性值提供事件處理器 返回 jQuery 集合 */ $('#example') .on('click', function (event) { console.log('At ' + formatDate(new Date()) + ' BOOM once!'); }) .on('click', function (event) { console.log('At ' + formatDate(new Date()) + ' BOOM twice!'); }) .on('click', function (event) { console.log('At ' + formatDate(new Date()) + ' BOOM thrice!'); }) .on('mouseenter mouseleave', function(event){ console.log(event.type) }); $('#btn1').on('click', function(){ console.log('The button is clicked!'); }).on('mouseenter mouseleave', myFunctionHandler); /* 使用 eventHash 參數類型的代碼 */ $('#btn2').on({ click: function(){ console.log('Oh no, you clicked me!'); }, mouseenter: myFunctionHandler, mouseleave: myFunctionHandler }); function myFunctionHandler(event){ event.stopPropagation(); console.log(event.type + ' ' + event.target.id); } /* 註意這裡的第二個參數,設置了 data 參數後,可以通過 event 參數的 data 屬性訪問 */ $('#btn1').on('click', { name: 'Martin_Fu' }, function(event){ console.log(event.data.name + ' clicked the button!'); }); /* 事件委托(event delegation) 這是一種向元素的父元素添加事件處理器的重要技術,可以為不存在的元素添加事件處理器 */ $('<li>item1</li>').add($('<li>item2</li>')).appendTo('#myList'); /* 原生 JS 寫法 */ document.getElementById('myList').addEventListener('click', function(event){ if(event.target.nodeName === 'LI'){ console.log('List item: ' + (Array.prototype.indexOf.call(document.getElementById('myList').children, event.target) + 1)); } }); /* 這裡註意第二個參數,它會對子元素進行篩選 事件委托的優勢不只局限於為不存在的元素執行事件處理器,更可以節省記憶體和時間。比如 myList 下有很多 <li> ,那麼需要迴圈 添加事件處理器,如果 <li> 元素很多,那麼會耗費不少時間,由於 JS 是單線程的,會導致不好的用戶體驗。 但也不能因為這個原因給一個元素(比如 document )添加過多處理器,同樣影響性能,建議儘可能為離目標元素近的元素添加處理器 */ $('#myList').on('click', 'li', function(event){ console.log('List item(jQuery): ' + ($(this).index() + 1)); }); /* one(eventType[, selector][, data], handler) one(eventHash[, selector][, data]) 為選擇的元素的一個或多個事件添加一個或多個事件處理器,一旦執行,事件處理器會自動刪除 返回 jQuery 集合 */ /* off(eventType[, selector][, data], handler) off(eventHash[, selector][, data]) 刪除參數指定的 jQuery 對象中所有元素的事件處理器,如果沒有提供參數,那麼刪除所有的元素處理器 返回 jQuery 集合 */ /* 這裡註意一點,如果多次點擊 btn-attach 按鈕,btn 按鈕會添加兩個 click 事件,但是只要點擊一次 btn-remove 按鈕,這兩個事件都會移除 */ var $btn = $('#btn'); var counter = 1; function logHandler(event){ console.log('Click ' + counter); counter++; } $('#btn-attach').on('click', function(event){ $btn.on('click', logHandler).text('Log'); }); $('#btn-remove').on('click', function(event){ $btn.off('click', logHandler).text('Does nothing'); });