我的認爲:不管是瀏覽器自帶的事件,仍是自定義事件,都是觀察者模式的實現。更確切地說:事件流是會流動的,流到哪一個節點,事件在哪裏發生,事件發生時,節點便會調用在這個節點綁定的事件處理程序。節點是被觀察者,事件處理程序是觀察者,當事件流流到被觀察者時,被觀察者會對外宣稱「我這裏發生了某個事件」,即通知觀察者,也就是節點調用事件處理程序。事件流是不知道被觀察者有多少個的,因此即便是0個,事件流也會繼續流,流到節點時,節點會遍歷本身註冊的事件處理程序,存在就調用。具體瀏覽器的實現和優化確定更加複雜和精妙,但原理應該是這樣(以上爲我的理解)。javascript
事件流分爲事件冒泡和事件捕獲:css
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>事件流</title> </head> <body> <div id="box">Click me</div> </body> </html>
如上圖所示,click首先在div元素上發生,而後沿着Dom樹向上傳播,每一級節點都會發生直至傳播到document對象。html
測試代碼java
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>事件流</title> </head> <style type="text/css"> #box1{ width: 300px; height: 300px; background-color: red; } #box2{ width: 200px; height: 200px; background-color: yellow; } #box3{ height: 100px; width: 100px; background-color: green; } </style> <body> <div id="box1"> <div id="box2"> <div id="box3"></div> </div> </div> </body> <script type="text/javascript"> document.getElementById('box1').onclick = function () { console.log('box1 click') } document.getElementById('box2').onclick = function () { console.log('box2 click') } document.getElementById('box3').onclick = function () { console.log('box3 click') } </script> </html>
測試效果:node
note: 幾乎現代全部的瀏覽器都支持事件冒泡,不過有一些細微的差異
IE5.5 和 IE5.5 - 版本的事件冒泡會跳過html元素(body 直接到 document)
IE九、Firefox、Chrome、Safari則一直冒泡到window對象。瀏覽器
Netscape提出的事件流模型稱爲事件捕獲。
即:事件從最不具體的節點開始接收(document),傳遞至最具體的節點<div>,和IE的冒泡恰好相反, 事件捕獲的本意是當事件到達預約目標前捕獲它。dom
當咱們點擊代碼片斷一中id爲box的div塊時,單擊事件會按照以下順序傳播:
document——> html ——> body ——> div函數
note: 雖然事件捕獲是Netscape惟一支持的事件流模型,但IE九、Firefox、Chrome、Safari目前也都支持這種事件模型,因爲老版本的瀏覽器並不支持,因此咱們應該儘可能使用事件冒泡,有特殊需求的時候再考慮事件捕獲。性能
爲了可以兼容上述兩種事件模型,又提出了一個DOM2級事件模型,它規定了事件流包含三個階段:測試
事件捕獲階段:爲事件捕獲提供機會;
處於目標階段:事件的目標接收到事件(但並不會作出響應);
事件冒泡階段:事件響應階段;
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>DOM2級事件流</title> </head> <style type="text/css"> #box1{ width: 300px; height: 300px; background-color: red; } #box2{ width: 200px; height: 200px; background-color: yellow; } #box3{ height: 100px; width: 100px; background-color: green; } </style> <body> <div id="box1"> <div id="box2"> <div id="box3"></div> </div> </div> </body> <script type="text/javascript"> var box1 = document.getElementById('box1'); var box2 = document.getElementById('box2'); var box3 = document.getElementById('box3'); // true表示在捕獲階段處理事件、false表示在冒泡階段處理 box1.addEventListener('click',function () { console.log('事件捕獲階段觸發box1點擊事件'); }, true); box1.addEventListener('click',function () { console.log('事件冒泡階段觸發box1點擊事件'); }, false); box2.addEventListener('click',function () { console.log('事件捕獲階段觸發box2點擊事件'); }, true); box2.addEventListener('click',function () { console.log('事件冒泡階段觸發box2點擊事件'); }, false) box3.addEventListener('click',function () { console.log('事件捕獲階段觸發box3點擊事件'); }, true); box3.addEventListener('click',function () { console.log('事件冒泡階段觸發box3點擊事件'); }, false) </script> </html>
傳統的事件處理中,須要爲每一個元素添加事件處理器。js事件代理則是一種簡單有效的技巧,經過它能夠把事件處理器添加到一個父級元素上,從而避免把事件處理器添加到多個子級元素上。
事件代理的原理用到的就是事件冒泡和目標元素,把事件處理器添加到父元素,等待子元素事件冒泡,而且父元素可以經過target(IE爲srcElement)判斷是哪一個子元素,從而作相應處理, 下面舉例說明:
傳統的事件會爲每一個dom添加事件,代碼以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>傳統的事件綁定</title> </head> <body> <ul id="color-list"> <li>red</li> <li>orange</li> <li>yellow</li> <li>green</li> <li>blue</li> <li>indigo</li> <li>purple</li> </ul> </body> <script> (function() { var colorList = document.getElementById("color-list"); var colors = colorList.getElementsByTagName("li"); for (var i = 0; i < colors.length; i++) { colors[i].addEventListener('click', showColor, false); }; function showColor(e) { e = e || window.event; var targetElement = e.target || e.srcElement; console.log(targetElement.innerHTML); } })(); </script> </script> </html>
事件代理的處理方式以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>傳統的事件綁定</title> </head> <body> <ul id="color-list"> <li>red</li> <li>orange</li> <li>yellow</li> <li>green</li> <li>blue</li> <li>indigo</li> <li>purple</li> </ul> <script> (function() { var colorList = document.getElementById("color-list"); colorList.addEventListener('click', showColor, false); function showColor(e) { e = e || window.event; var targetElement = e.target || e.srcElement; if (targetElement.nodeName.toLowerCase() === "li") { alert(targetElement.innerHTML); } } })(); </script> </body> </html>
使用事件代理的好處:
將多個事件處理器減小到一個,由於事件處理器要駐留內存,這樣就提升了性能。想象若是有一個100行的表格,對比傳統的爲每一個單元格綁定事件處理器的方式和事件代理(即table上添加一個事件處理器),不可貴出結論,事件代理確實避免了一些潛在的風險,提升了性能。
DOM更新無需從新綁定事件處理器,由於事件代理對不一樣子元素可採用不一樣處理方法。若是新增其餘子元素(a,span,div等),直接修改事件代理的事件處理函數便可,不須要從新綁定處理器,不須要再次循環遍歷。
var EventUtil = { getEvent: function(event){ return event ? event : window.event; // window.event DOM0級時IE }, getTarget: function(event){ return event.target || event.srcElement; // event.srcElement for IE }, preventDefault: function(event){ if (event.preventDefault){ event.preventDefault(); } else { event.returnValue = false; // IE } }, stopPropagation: function(event){ if (event.stopPropagation){ event.stopPropagation(); } else { event.cancelBubble = true; // IE } } }; var EventUtil = { addHandler: function(element, type, handler){ if (element.addEventListener){ element.addEventListener(type, handler, false); } else if (element.attachEvent){ element.attachEvent("on" + type, handler); } else { element["on" + type] = handler; } }, removeHandler: function(element, type, handler){ if (element.removeEventListener){ element.removeEventListener(type, handler, false); } else if (element.detachEvent){ element.detachEvent("on" + type, handler); } else { element["on" + type] = null; } } };
事件委託 <body> <ul id="myLinks"> <li id="goSomewhere">Go somewhere</li> <li id="doSomething">Do something</li> <li id="sayHi">Say hi</li> </ul> <script type="text/javascript"> (function(){ var list = document.getElementById("myLinks"); EventUtil.addHandler(list, "click", function(event){ event = EventUtil.getEvent(event); var target = EventUtil.getTarget(event); switch(target.id){ case "doSomething": document.title = "I changed the document's title"; break; case "goSomewhere": location.href = "http://www.wrox.com"; break; case "sayHi": alert("hi"); break; } }); })(); </script> </body>
click
、mousedown
、mouseup
、keydown
、keyup
和 keypress