javascript與HTML之間交互就是經過事件實現的,事件就是文檔或瀏覽器窗口中發生的一些特定的交互瞬間。事件在瀏覽器中是以對象的形式存在的,即event,觸發一個事件,就會產生一個事件對象event,該對象包含着全部與事件有關的信息,包括致使事件的元素、事件的類型以及其餘與特定事件相關的信息。javascript
<button onclick="clickFun()">addEvent</button>
a. element.onclick=function(e){}
b. element.addEventListener(event,function(e),useCapture)java
c. element.attachEvent(event,function(e))node
<button id="myBtn">點擊這裏</button> <script> document.getElementById("myBtn").onclick=function(){clickFun()}; document.getElementById("myBtn").addEventListener("click",function(event){ clickFun(); },false); //採用事件冒泡方式給dom元素註冊click事件 document.getElementById("myBtn").addEventListener("click",function(event){ clickFun(); },true); //採用事件捕獲方式給dom元素註冊click事件 document.getElementById("myBtn").attachEvent("click",function(event){ clickFun(); }); </script>
擴展:原生Javascript寫法,跨瀏覽器添加事件瀏覽器
var EventUtil = { // 添加事件監聽 add: function(element, type, callback){ if(element.addEventListener){ element.addEventListener(type, callback, false); } else if(element.attachEvent){ element.attachEvent('on' + type, callback); } else { element['on' + type] = callback; } } // 移除事件監聽 remove: function(element, type, callback){ if(element.removeEventListener){ element.removeEventListener(type, callback, false); } else if(element.detachEvent){ element.detachEvent('on' + type, callback); } else { element['on' + type] = null; } } }
<button id="myBtn">點擊這裏</button> <script> var myBtn = document.getElementById("myBtn"); EventUtil.add(myBtn, 'click', function(){ console.log('被點擊了'); });
事件捕獲(event capturing):當鼠標點擊或者觸發dom事件時,瀏覽器會從根節點開始由外到內進行事件傳播,即點擊了子元素,若是父元素經過事件捕獲方式註冊了對應的事件的話,會先觸發父元素綁定的事件。性能優化
事件冒泡(dubbed bubbling):與事件捕獲偏偏相反,事件冒泡順序是由內到外進行事件傳播,直到根節點。app
注:不管是事件捕獲仍是事件冒泡,它們都有一個共同的行爲,就是事件傳播dom
event.stopPropagation() event.stopImmediatePropagation()
舉個栗子:此栗子是阻止事件捕獲,阻止事件冒泡原理同樣函數
<div id="div1"> <button id="button1">測試</button> </div> <script type="application/javascript"> var div1 = document.getElementById("div1"); var btn = document.getElementById("button1"); div1.addEventListener("click", function () { alert("div1第一次執行"); event.stopPropagation(); }, true); div1.addEventListener("click", function () { alert("div1第二次執行"); }, true); btn.addEventListener("click", function (event) { alert("button 執行"); }, true); </script> //執行結果 alert("div1第一次執行"); alert("div1第二次執行");
<div id="div1"> <button id="button1">測試</button> </div> <script type="application/javascript"> var div1 = document.getElementById("div1"); var btn = document.getElementById("button1"); div1.addEventListener("click", function () { alert("div1第一次執行"); event.stopImmediatePropagation(); }, true); div1.addEventListener("click", function () { alert("div1第二次執行"); }, true); btn.addEventListener("click", function (event) { alert("button 執行"); }, true); </script> //執行結果 alert("div1第一次執行");
事件代理也能夠稱之爲事件委託,事件代理就是在祖先級DOM元素綁定一個事件,當觸發子孫級DOM元素的事件時,利用事件冒泡的原理來觸發綁定在祖先級DOM的事件。性能
那這是什麼意思呢?網上的各位大牛們講事件代理基本上都用了同一個例子,就是取快遞來解釋這個現象,借花獻佛,你們認真領會一下事件代理究竟是一個什麼原理:
有三個同事預計會在週一收到快遞。爲簽收快遞,有兩種辦法:一是三我的在公司門口等快遞;二是委託給前臺MM代爲簽收。現實當中,咱們大都採用委託的方案。前臺MM收到快遞後,她會判斷收件人是誰,而後按照收件人的要求籤收,甚至代爲付款。這種方案還有一個優點,那就是即便公司裏來了新員工,前臺MM也會在收到寄給新員工的快遞後覈實並代爲簽收。測試
這裏其實有2層意思的:
第一,如今委託前臺的同事是能夠代爲簽收的,即程序中的現有的dom節點是有事件的;
第二,新員工也是能夠被前臺MM代爲簽收的,即程序中新添加的dom節點也是有事件的。
通常來講,dom須要有事件處理程序,咱們都會直接給它設事件處理程序就行了,那若是是不少的dom須要添加事件處理呢?好比咱們有100個li,每一個li都有相同的click點擊事件,可能咱們會用for循環的方法,來遍歷全部的li,而後給它們添加事件,那這麼作會存在什麼影響呢?
在JavaScript中,添加到頁面上的事件處理程序數量將直接關係到頁面的總體運行性能,由於須要不斷的與dom節點進行交互,訪問dom的次數越多,引發瀏覽器重繪與重排的次數也就越多,就會延長整個頁面的交互就緒時間,這就是爲何性能優化的主要思想之一就是減小DOM操做的緣由;若是要用事件代理,就會將全部的操做放到js程序裏面,與dom的操做就只須要交互一次,這樣就能大大的減小與dom的交互次數,提升性能;
每一個函數都是一個對象,是對象就會佔用內存,對象越多,內存佔用率就越大,天然性能就越差了(內存不夠用,是硬傷),好比上面的100個li,就要佔用100個內存空間,若是是1000個,10000個呢,那隻能呵呵了,若是用事件代理,那麼咱們就能夠只對它的父級(若是隻有一個父級)這一個對象進行操做,這樣咱們就須要一個內存空間就夠了,是否是省了不少,天然性能就會更好。
在介紹事件代理的方法以前,先來看一段通常方法的栗子:
<ul id="ulDelegate"> <li>111</li> <li>222</li> <li>333</li> <li>444</li> </ul> <script> window.onload = function(){ var ulDelegate= document.getElementById("ulDelegate"); var liDelegate= ulDelegate.getElementsByTagName('li'); for(var i=0;i<liDelegate.length;i++){ liDelegate[i].onclick = function(){ alert('noDelegate'); } } } </script>
上面的代碼很簡單,咱們看看有多少次的dom操做,首先要找到ul,而後遍歷li,而後點擊li的時候,又要找一次目標的li的位置,才能執行最後的操做,每次點擊都要找一次li。
用事件代理的方式來實現,上栗子:
window.onload = function(){ var ulDelegate= document.getElementById("ulDelegate"); ulDelegate.onclick = function(){ alert('Delegate'); } }
這裏用父級ul作事件處理,當li被點擊時,因爲冒泡原理,事件就會冒泡到ul上,由於ul上有點擊事件,因此事件就會觸發,固然,這裏當點擊ul的時候,也是會觸發的。
那麼問題就來了,若是我想讓事件代理的效果跟直接給節點的事件效果同樣怎麼辦,好比說只有點擊li纔會觸發,這時,Event對象就派上用場了,Event提供了一個屬性叫target,能夠返回事件的目標節點,咱們稱爲事件源,也就是說,target就能夠表示爲當前的事件操做的dom,可是不是真正操做dom,這個是有兼容性的,標準瀏覽器用ev.target,IE瀏覽器用event.srcElement,此時只是獲取了當前節點的位置,並不知道是什麼節點名稱,這裏咱們用nodeName來獲取具體是什麼標籤名,這個返回的是一個大寫的,咱們能夠轉成小寫再作比較,或者不轉。
window.onload = function(){ var ulDelegate= document.getElementById("ulDelegate"); ulDelegate.onclick = function(ev){ var ev = ev || window.event; var target = ev.target || ev.srcElement; if(target.nodeName.toLowerCase() == 'li'){ alert('Delegate'); alert(target.innerHTML); } } }
這樣就只有點擊li會觸發事件了,且每次只執行一次dom操做,若是li數量不少的話,將大大減小dom的操做,優化的性能可想而知!
上面的栗子是說li操做的是一樣的效果,若是每一個li被點擊的效果都不同,上栗子:
<ul id="ulDelegate"> <li id='add'>添加</li> <li id='delete'>刪除</li> <li id='edit'>修改</li> </ul> <script> window.onload = function(){ var ulDelegate= document.getElementById("ulDelegate"); ulDelegate.onclick = function (ev) { var ev = ev || window.event; var target = ev.target || ev.srcElement; if(target.nodeName.toLocaleLowerCase() == 'li'){ switch(target.id){ case 'add' : alert('添加'); break; case 'delete' : alert('刪除'); break; case 'edit' : alert('移動'); break; } } } } </script>
再來最後一個事件代理的栗子,當咱們新增一個li元素的時候,一樣先看一下通常寫法:
<ul id="ulDelegate"> <li>111</li> <li>222</li> <li>333</li> <li>444</li> </ul> <button id="addLi">新增li</button> <script> var ulDelegate= document.getElementById("ulDelegate"); var liDelegate= ulDelegate.getElementsByTagName('li'); var addLi = document.getElementById("addLi"); function addClick() { for(var i=0;i<liDelegate.length;i++){ liDelegate[i].onclick = function(){ alert('noDelegate'); } } } addClick(); addLi.onclick = function(){ var addLiElement = document.createElement('li'); addLiElement .innerHTML = 10000*Math.random(); ulDelegate.appendChild(addLiElement ); addClick(); } </script>
再來看一下事件代理的寫法:
window.onload = function(){ var ulDelegate = document.getElementById("ulDelegate"); var addLi = document.getElementById("addLi"); ulDelegate.onclick = function(ev){ var ev = ev || window.event; var target = ev.target || ev.srcElement; if(target.nodeName.toLowerCase() == 'li'){ alert('Delegate'); alert(target.innerHTML); } }; addLi.onclick = function(){ var addLiElement = document.createElement('li'); addLiElement .innerHTML = 10000*Math.random(); ulDelegate.appendChild(addLiElement ); } }
看,上面是用事件單例的方式,新添加的子元素是帶有事件效果的,咱們不須要去遍歷元素的子節點,只須要給父級元素添加事件就行了,其餘的都是在js裏面的執行,這樣能夠大大的減小dom操做,這纔是事件代理的精髓所在。
事件是將javascript與網頁聯繫在一塊兒的主要方式,在使用事件時,須要考慮以下一些內存與性能方面的問題。
事件是javascript中最重要的主題之一,深刻理解事件的工做機制以及它們對性能的影響是相當重要的
(總結摘自javascript高級程序設計第三版)