javascript事件基礎知識

婉婉發表於2019-03-12

什麼是事件

javascript與HTML之間互動就是透過事件實現的,事件就是文件或瀏覽器視窗中發生的一些特定的互動瞬間。事件在瀏覽器中是以物件的形式存在的,即event,觸發一個事件,就會產生一個事件物件event,該物件包含著所有與事件有關的資訊,包括導致事件的元素、事件的型別以及其他與特定事件相關的資訊。

DOM元素新增事件

1.DOM元素事件屬性

<button onclick="clickFun()">addEvent</button>

2.javascript指令碼新增事件

a. element.onclick=function(e){}
b. element.addEventListener(event,function(e),useCapture)

  • 適用範圍:現代瀏覽器(IE9+, Firefox , chorme, safari, opera)
  • 引數介紹:event必須,事件名稱。(注:不含“on”,比如“click”、“mouseover”等)。function必須,指定事件觸發時執行的函式。useCapture可選,布林值,指定事件是否在捕獲或冒泡階段執行,預設false(冒泡)。

c. element.attachEvent(event,function(e))

  • 適用範圍:IE 6、7、8
  • 引數介紹:event 必須,字串,事件名稱。(注:含“on”,比如“onclick”、“onmouseover”等)。function必須,指定事件觸發時執行的函式。
<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事件時,瀏覽器會從根節點開始由外到內進行事件傳播,即點選了子元素,如果父元素透過事件捕獲方式註冊了對應的事件的話,會先觸發父元素繫結的事件。

clipboard.png

事件冒泡

事件冒泡(dubbed bubbling):與事件捕獲恰恰相反,事件冒泡順序是由內到外進行事件傳播,直到根節點。

clipboard.png

注:無論是事件捕獲還是事件冒泡,它們都有一個共同的行為,就是事件傳播

阻止事件捕獲/事件冒泡

 event.stopPropagation()   
 event.stopImmediatePropagation()
  • event.stopPropagation()
    會阻止事件繼續分發到其他document節點,但是當前節點繫結的多個事件會繼續按註冊的順序執行
  • event.stopImmediatePropagation()
    不僅阻止事件繼續分發到其他document,還會將事件分發就地停止,在當前事件之後註冊的其他事件,都不會執行

舉個例子:此栗子是阻止事件捕獲,阻止事件冒泡原理一樣

<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與網頁聯絡在一起的主要方式,在使用事件時,需要考慮如下一些記憶體與效能方面的問題。

  1. 有必要限制夜歌頁面中事件處理程式的數量,數量太多會導致佔用大量記憶體,而且也會讓使用者頁面反應不夠靈敏
  2. 建立在事件冒泡機制之上的時間委託技術,可以有效的減少時間處理程式的數量
  3. 建議在瀏覽器解除安裝頁面之前移除頁面中的所有事件處理程式

事件是javascript中最重要的主題之一,深入理解事件的工作機制以及它們對效能的影響是至關重要的

(總結摘自javascript高階程式設計第三版)

相關文章