事件處理

建立 JavaScript 事件處理器就是將一個事件、一個物件與 JavaScript 函數相關聯,例如 window.onload = init;,這行程式碼表示發生視窗的 load 事件時,應該呼叫 init() 函數,正式來講,可以說已建立或「註冊」一個事件監聽器(Event Listener),init() 函數將作為視窗物件載入事件的事件處理器。
參考 MSDN 管理事件監聽程式:https://msdn.microsoft.com/zh-tw/library/dn741342(v=vs.94).aspx

1建立事件監聽器

JavaScript 支援四種不同的事件監聽器建立方式:

1. 嵌入式事件處理器

(不建議使用)
將 JavaScript 函數指派給 HTML 元素屬性,此法容易編寫,曾十分盛行,且在所有瀏覽器能執行,但是應避免這種做法,因為會汙染 HTML 碼,將 JavaScript 散佈到 HTML 頁面的各處,不但不美觀,還會導致程式人員難以偵錯和管理專案,其次,在 HTML 直接撰寫 JavaScript 程式時,無法應用漸進式改善的概念。
          
...
Some Link
後者程式意指,當使用者點擊時,由 doSomething() 函數完成必要的工作,但若無有效的 href 值,在 JavaScript 被停用或不可用時絕對無法處理此連結,對使用者來說,最終結果是網頁似乎已被破壞,因為網頁上的連結沒有任何作用。

2. 傳統事件處理


通常都會採用這種方法,因為容易使用,而且在近十年來發佈的每種瀏覽器上都能可靠運作
注意使用傳統事件指定事件處理器時,使用屬性須為「全小寫字母」,例如:onload、onsubmit
          window.onload = init;

          window.onload = function(){}      //完全適用於匿名函數,不必正式宣告

          window.onload = null;             //將物件的相關事件屬性設為 null 為刪除事件處理器
        

和任何物件屬性一樣,可以檢查物件屬性值,確認事件監聽器的存在:
          if(typeof window.onload == 'function'){}
        

而不使用傳統方法的原因有兩個:1.這種方法一次只能監聽一個事件,2.很容易覆蓋現有的事件處理器
同時呼叫兩個監聽器,只會呼叫後者,前者有關的原始事件處理器已經被替換:
          document.getElementbyId('theForm').onsubmit = process;
          document.getElementbyId('theForm').onsubmit = calculate;    //提交表單後,只會呼叫 calculate() 函數
        
上述問題可以建立一個呼叫兩個函數的事件處理器來解決,但程式碼醜陋,最好不要使用,而且在複雜的專案中有多個 JavaScript 檔案和程式庫,如果 JavaScript 程式使用傳統方法,有可能在其他檔案中已撤銷該事件註冊:
          document.getElementbyId('theForm').onsubmit = function(){
            process();
            calculate();
          }
        

3. W3C 事件處理


W3C(World Wide Web Consortium)提出自己的事件監聽器建立方法,以作為 DOM Level 2 規範的一部份(DOM 規範還定義了事件處理之外的其他瀏覽器行為),此法以兩種方式改善嵌入式和傳統方法:1.賦值在 JavaScript 中完成,並非在 HTML2.允許相同元素的多個事件處理器。W3C DOM Level 2 方法(嵌入和傳統事件處理被統稱為 DOM Level 0,也就是在事件處理標準未出現前存在的方法)符合邏輯、靈活並易於應用,雖程式碼較傳統多,但幾乎所有瀏覽器都支援,只有 IE9 之前的版本不支援

第三個參數,可以始終填入 false 或直接忽略──導致類似假值的 undefined
          window.addEventListener('load', init, false);         //新增事件監聽器


          //多次呼叫 addEventListener() 方法,就能為相同元素的同一事件增加多個事件監聽器
          window.addEventListener('load', process, false);
          window.addEventListener('load', calculate, false);


          window.removeEventListener('load', process, false);   //刪除事件監聽器,此時只會執行 calculate() 函數
        
這裡使用 load 而不是 onload:load 是事件名稱;onload 是 window 物件的對應屬性
不再需要事件監聽器時應刪除,但是許多程式人員都沒有這麼做,如果試圖刪除不存在的事件監聽器,並不會拋出錯誤。

4. IE 瀏覽器事件處理


在 DOM 事件監聽器上,IE 採用不同方法,所幸 IE9 支援 W3C DOM,從 DOM Level 0 事件監聽器轉移而來的開發人員須轉寫跨平台相容事件處理器。用 attachEvent()detachEvent() 代替 addEventListener()removeEventListener(),兩個方法都只有兩個參數。

          window.attachEvent('onload', init);         //新增事件監聽器

          window.detachEvent('onload', init);         //刪除事件監聽器
        
事件名稱與 W3C DOM 的 load 不同,與傳統方法相同

建立事件分配器


綜觀以上,編寫跨瀏覽器風格的 window.onload 程式增加漸進式改善,(為求精確,可對每個條漸檢查屬性 typeof 是否為函數):
          if(window.addEventListener){                      //W3C
          	window.addEventListener('load', init, false);
          }else if(window.attachEvent){                     //olderIE
          	window.attachEvent('onload', init);
          }
        

減少大量重複編寫條件敘述,可利用函數的引數替換對應的特定內容:
          function addEvent(obj, type, fn){
          	if(obj && obj.addEventListener){                //W3C
          		obj.addEventListener(type, fn, false);
          	}else if(obj && obj.attachEvent){               //olderIE
          		obj.attachEvent('on' + type, fn);           //olderIE 寫法要在事件名稱前加上 on 字眼
          	}
          }

          addEvent(window, 'load', init);                   //對於所有瀏覽器,可使用此程式碼呼叫
        
用 addEventListener() 和 attachEvent() 增加事件並不會覆蓋傳統方法指定的事件處理器

命名事件處理器


從風格的角度出發,開發人員往往使用事件和元素的組合來命名事件處理函數,如:
大多偏愛描述函數用途的函數名稱,而不是呈現其事件處理器角色的名稱,無論如何都要堅持一種風格,並保持一致。

2建立工具程式庫

了解如何在任何瀏覽器上可靠指定事件監聽器方法之後,就能開始撰寫更多基於事件的程式,為了避免多個新函數汙染全域的命名空間,所有函數都定義為一個全域物件(簡單地命名為 U ──工具程式 Utility 的縮寫),以此檔案作為開發 JavaScript 跨瀏覽器監聽事件的模組:
          //建立新物件 U,是這個腳本中唯一的全域變數
          var U = {
              //定義 $() 方法,該函數的參數是想要讀取元素的 id,並返回元素的參照
              $: function(id) {
                  'use strict';
                  if (typeof id == 'string') {
                      return document.getElementById(id);
                  }
              },
              //定義 setText() 方法,更新的元素 id 和訊息
              setText: function(id, message) {
                  'use strict';
                  if ( (typeof id == 'string') && (typeof message == 'string') ) {
                      var output = this.$(id);        //呼叫 $() 函數時使用 this.$() 或者 U.$()

                      //如果沒有找到對應的元素,該函數返回 false;反之返回 true
                      if (!output) return false;

                      if (output.textContent !== undefined) {
                          output.textContent = message;
                      } else {
                          output.innerText = message;
                      }
                      return true;
                  }
              },
              //定義 addEvent() 方法,增加事件監聽器
              addEvent: function(obj, type, fn) {     //注意,函數的第一個引數是物件,而非目標元素 id
                  'use strict';
                  if (obj && obj.addEventListener) {
                      obj.addEventListener(type, fn, false);
                  } else if (obj && obj.attachEvent) {
                      obj.attachEvent('on' + type, fn);
                  }
              },
              //定義 removeEvent() 方法,刪除事件監聽器
              removeEvent: function(obj, type, fn) {
                  'use strict';
                  if (obj && obj.removeEventListener) {
                      obj.removeEventListener(type, fn, false);
                  } else if (obj && obj.detachEvent) {
                      obj.detachEvent('on' + type, fn);
                  }
              }
          };
        

3事件類型

在 Web 瀏覽器中可能發生的事件有四大類:
(並不是全部瀏覽器和裝置都支援任何的事件類型,像是螢幕閱讀器沒有輸入裝置、觸控事件僅能用於可觸控的裝置上)

輸入裝置事件


包括滑鼠、觸控板、軌跡球、繪圖板以及類似的裝置觸發,專指游標驅動的事件,其中又分為按鈕和移動事件:

按鈕事件


            var events = ['mousedown', 'mouseup', 'click', 'dblclick', 'contextmenu'];

            for(var i = 0 ;i < events.length; i++){
              var id = U.$(events[i]);                    //抓取 id
              U.addEvent(id, events[i], variation);       //新增事件監聽器
              function variation(){
                this.innerHTML = "Done!";    //觸發後改變文字
                U.removeEvent(id, events[i], variation);  //移除事件監聽器
              }
            }
          

移動事件


在 IE 和 Opera 的實用事件,表現比 mouseover 和 mouseout 好,但有些瀏覽器並不支援
            var events = ['mouseover', 'mouseout', 'mousemove', 'mouseenter', 'mouseleave'];

            for(var i = 0 ;i < events.length; i++){
              var id = U.$(events[i]);
              U.addEvent(id, events[i], variation);
              function variation(){
                this.innerHTML = "Done!";
                U.removeEvent(id, events[i], variation);
              }
            }
          
            
觸控裝置和輸入事件
ipod、平板電腦、智慧型手機等觸控螢幕裝置的出現與興起,為輸入事件帶來更多挑戰,這些裝置支援以上輸入事件,但無法完成滑鼠和按鍵同時作用的事件,觸控裝置也有新的事件和手勢(Gesture),有關這方面的更多資訊可參考專注於行動裝置 JavaScript 的開發資源。

輸入鍵盤事件


若干瀏覽器輸入常見的作業系統命令時(如 Ctrl + s)不會呼叫事件處理器,按住一鍵不放將會出發多次 keydown 事件。
發生 keypress 事件時保留「字元碼」;而發生 keydown 和 keyup 時則保留「按鍵碼」
            'use strict';
            var events = [['keydown','keydown'], ['keyup','keyup'], ['keypress','keypress'], ['anykey1','anykey(keydown)'], ['anykey2','anykey(keyup)'], ['anykey3','anykey(keypress)']];

            U.addEvent(window, 'keydown', function(e){
              if(e.keyCode == 65) text('keydown', e);
            });
            U.addEvent(window, 'keyup', function(e){
              if(e.keyCode == 83) text('keyup', e);
            });
            U.addEvent(window, 'keypress', function(e){
              if(e.keyCode == 68) text('keypress', e);
            });
            U.addEvent(window, 'keydown', function(e){
              text('anykey1', e);
            });
            U.addEvent(window, 'keyup', function(e){
              text('anykey2', e);
            });
            U.addEvent(window, 'keypress', function(e){
              text('anykey3', e);
            });

            function text(id, e){
              var str;
              for(var i = 0 ;i < events.length; i++){
                if(id == events[i][0])
                str = events[i][1];
              }
              var charCode = (typeof e.which == 'number') ? e.which : e.keyCode;
              U.$(id).innerHTML = str + "
keyNum:" + charCode + ", letter:" + String.fromCharCode(e.which) + ""; }
參考 http://www.asciitable.com/ 的 ASCII code 表:http://www.asciitable.com/

瀏覽器事件


瀏覽器本身


與編輯選單命令相關


與「編輯」選單相關,在 IE 和 Firefox 之外的瀏覽器未得到可靠支援,且不建議使用防止竊取資料用途,因為一旦停用 JavaScript 就無效,而且1.這是一種愚蠢且不可靠的做法、2.JavaScript 不該破壞瀏覽器或者用於和使用者預期相對立的工作、3.網頁的性質是共用資訊的一種手段,當使用者載入頁面後,內容就以出現在他的電腦上,做任何防禦動作都是無意義的

與編輯選單命令相關


當開啟多的瀏覽器視窗時,最前面的視窗會觸發 focus;而在後的視窗則觸發 blur
            U.addEvent(window, 'onload', function(){
              U.$('onload').innerHTML = "Done!";
            });
            U.addEvent(window, 'unload', function(){
              U.$('unload').innerHTML = "Done!";
              alert('unload!');
            });
            U.addEvent(window, 'beforeunload', function(){
              U.$('beforeunload').innerHTML = "Done!";
              alert('beforeunload!');
            });
            U.addEvent(window, 'scroll', function(){
              U.$('scroll').innerHTML = "Done!";
            });
            U.addEvent(window, 'copy', function(){
              U.$('copy').innerHTML = "Done!";
            });
            U.addEvent(window, 'cut', function(){
              U.$('cut').innerHTML = "Done!";
            });
            U.addEvent(window, 'paste', function(){
              U.$('paste').innerHTML = "Done!";
            });
            U.addEvent(window, 'focus', function(){
              U.$('focus').innerHTML = "Done!";
            });
            U.addEvent(window, 'blur', function(){
              U.$('blur').innerHTML = "Done!";
            });
            //以上事件每個瀏覽器的支援度都不太相同
          

表單事件


            U.addEvent(U.$('form'), 'change', function(){
              U.$('show').innerHTML = "change!";
            });
            U.addEvent(U.$('form'), 'select', function(){
              U.$('show').innerHTML = "select!";
            });
            U.addEvent(U.$('text1'), 'blur', function(){
              U.$('show2').innerHTML = "blur!";
            });
            U.addEvent(U.$('text1'), 'focus', function(){
              U.$('show2').innerHTML = "focus!";
            });
            U.addEvent(U.$('form'), 'reset', function(){
              U.$('show').innerHTML = "reset!";
            });
            U.addEvent(U.$('form'), 'submit', function(){
              U.$('show').innerHTML = "submit!";
            });
          
參考 w3schools 新增事件參考:http://www.w3schools.com/jsref/dom_obj_event.asp

4事件存取性

確定需要監視的事件時,必須考慮存取性(Accessibility),過去建立可存取的頁面是為了確保使用螢幕閱讀器(用於視力受損者的裝置)的人能夠成功使用網站,但隨著行動裝置和其他非標準瀏覽器的出現,更應注意哪些才是重要與可靠的事件,像是針對不使用輸入裝置的瀏覽而言,mouseover 和 mouseout 是毫無意義,因此對各種客戶端都能安全使用事件處理的方法是建立兩個事件監聽器,此概念被稱作「配對事件(Pairing events)」:使用一個函數處理相同元素的類似事件,為改善可存取性,最好將事件監聽器與表單提交動作(而非點擊提交按鈕)連結。
如果是做 mouseover 事件處理器,就由鍵盤控制的 focus 事件代替;反之,mouseout 就由 blur 代替:
          Some Text
        
          addEvent(document.getElementById('link'), 'mouseover', doSomething);
          addEvent(document.getElementById('link'), 'focus', doSomething);
        

5事件和漸進式改善

漸進式改善的原則為 JavaScript(和 CSS)改進基本功能,使無論使用何種裝置都能支援。只有在有意識地排除某些使用者時,JavaScript 才是必要的,像是在網頁上用 JavaScript 編寫的遊戲就無法建立一個非 JavaScript 的版本,這樣情況下排除某些使用者是合理的。但在多數情況下,例如表單驗證,在此 JavaScript 是沒有必要的,應該在有無 JavaScript 的情況下都能進行(伺服端仍進行驗證)。

上述概念應用到事件處理,代表必須小心增加事件至已有預設事件行為的元素,例如表單目的是提交,並傳給伺服端,用 click 事件為表單增加提交事件以及其他功能是有意義的。
而以下情況無法避免排除停用 JavaScript 的使用者: 另外可以使用替代方案,漸進式改善 JavaScript:

6進階事件處理

引用事件


當事件處理用於多個元素的相同事件(例如點擊連結)或相同元素的多個事件(滑鼠移入或焦點移到按鈕上)時,就必須考慮事件處理器的靈活性,如此一來,事件本身的存取變得相當有用,存取的方式取決所用的瀏覽器,ewindow.event 皆指事件處理器自動接收一參數,代表發生的事件,可自行編寫處理器接收此參數,參數經常縮寫為 e 或 evt(事件 event 的縮寫)
          function someEventHandler(e){   //使用 e,支援 addEventListener() 方法的流覽器
          	...
          }
          function someEventHandler(){    //使用 window.event,IE8 以下的瀏覽器,僅支援 attachEvent() 方法
          	...
          }


          function someEventHandler(e){   //一般函數編寫方式,可以可靠地引用事件而不考慮使用的瀏覽器
            if(typeof e == 'undefined')
              e = window.event;
          }
        
存取事件有三種寫法可以編寫:
          if(typeof e == 'undefined')     //用 typeof 判斷,e 不存在就返回 window.event
            e = window.event;

          if(!e)                          //e 不存在就返回 window.event
            e = windows.event;

          e = e || window.event;          //用 || 判斷式,true 就返回 e;反之返回 window.event
        

不管透過 DOM 方法或傳統方法建立事件處理器,以上方法都有效,甚至對匿名函數也一樣:
          someElement.onclick = function(e){
            if(typeof e == 'undefined')
              e = window.event;
          }
        
若為嵌入式,就必須將事件明確傳遞給函數:
          <a href="somepage.html" onclick="dosomething(event);">Link</a>
        

事件屬性


存取事件的好處之一:無論源自於傳遞到函數的值還是 window 物件,事件物件都透過各種屬性提供有用的資訊,但也取決於所用的瀏覽器,tagetsrcElement指向觸發事件的 HTML 元素
            var target = e.target || e.srcElement;
          
另一個有用的屬性是 type:
            var target = e.type;
          
              U.addEvent(U.$('button'), 'click', function(e){
                if(typeof e == 'undefined')
                  e = window.event;
                var target = e.target || e.srcElement;

                U.$('show').innerHTML = "target.nodeName:" + target.nodeName + ", e.type:" + e.type;
              });
            
            
重溫 this 變數
特殊變數 this,代表呼叫方法所在的物件(如果不是在物件上直接呼叫函數,則表 window 物件),在傳統方法或 addEventListener() 建立事件處理器時,this 變數可能是觸發事件的 HTML 元素;IE8 以前的版本,事件處理器的 this 引用全域變數 window,因此不起作用。
其他事件屬性參考:http://www.w3.org/TR/WD-DOM-Level-2/events.html#Events-interface

檢查按鍵


觸發鍵盤事件時,可以透過事件物件確定按下的具體鍵,但在各種瀏覽器上也有差別。首先考慮的是「鍵」和「字元」的差別,鍵是指鍵盤上的實體按鍵,但大部分鍵可用來產生超過一個字元,例如 A 鍵可以輸入 A 或者 a,取決於是否同時按下 Shift 鍵(A 和 a 的按鍵編碼都是 65,A 的字元碼是 65,而 a 的字元碼則是 97)。
按鍵事件中有 keyCodewhich 屬性通常代表字元值(Unicode): 但當發生 keypress 事件時保留「字元碼」;而發生 keydown 和 keyup 時則保留「按鍵碼」,IE8 以下不支援 which 屬性,其他瀏覽器以 which 屬性儲存按鍵的「字元碼」,為使一致性,可用如下程式碼:
            var charCode = e.which || e.keyCode;

            var charCode = (typeof e.which == 'number') ? e.which : e.keyCode;    //更準確的寫法
          
若要取得與字元碼相關的實際字元,可用 String 物件的 fromCharCode() 方法:
            String.fromCharCode(charCode);
          
              U.addEvent(window, 'keypress', function(e){
                var charCode = (typeof e.which == 'number') ? e.which : e.keyCode;
                U.$('show').innerHTML = "keyNum:" + charCode + ", letter:" + String.fromCharCode(charCode) + "";
              });
            

在某些情況下(如建立遊戲和其他圖形介面時),字元並不重要,關鍵是按下的特定游標鍵,此刻需監視 keydown 和 keyup 事件,就可以在所有瀏覽器中可靠地使用 keyCode。
三個特殊鍵具備特殊屬性: 每個屬性代表是否按下該鍵(不管是否和其他鍵同時按下)的布林值,Mac 沒有 Alt 鍵,altKey 代表是否按下 Option 鍵,通常按下這些鍵時都不觸發 keypress 事件,因為特殊鍵只有在與另一個鍵組合才有意義。
            
游標和滑鼠屬性
輸入裝置事件所建立的是建物件有自己的獨特屬性,以 mousedown 和 mouseup 事件為例(不適用 click 事件),which 和 button 屬性可用來查看按下的滑鼠按鈕,不同瀏覽器採用不一樣的值,唯一一致的是點擊右鍵的 button 值為 2,但想要有效使用這些屬性有些困難。relatedTarget 是一個有用的屬性:在 mouseout 事件中,代表游標離開觸發事件的元素後進入的元素。
其他事件屬性參考:http://www.w3.org/TR/WD-DOM-Level-2/events.html#Events-interface

阻止預設事件行為


通常瀏覽器行為相關的 JavaScript 事件:點擊連結、提交表單等,預設回傳值是 true(存在事件處理器時,事件的發生會呼叫對應的函數,當執行函數之後,瀏覽器仍會繼續進行後頭的事件處理)。在一些情況下並不希望發生瀏覽器的預設行為,例如提交表單,若是提交錯誤,回傳 false,阻止表單提交,讓使用者有機會修正錯誤。
            function handleForm(){
              if(errors)
                return false;
              else
                return true;
            }
          
對於不支援 addEventListener() 方法的瀏覽器來說,阻止預設事件行為的替代方案是呼叫事件物件的 preventDefault() 方法,針對 IE9 之前的版本,應該設定事件物件的 returnValue 屬性為 false。
使用 e.preventDefault()e.returnValue 的好處是,可以在函數開始時進行此項工作,以便執行函數後續的程式碼;相反的,在執行完 return 敘述後,函數立即終止,當返回 false 將阻止相同事件呼叫其他事件處理器,且終止事件的「冒泡階段(Bubbling phase)」。
匯總以上方法,編寫跨瀏覽器程式碼:
            if(typeof e == 'undefined')
                e = window.event;

            if(e.preventDefault){
              e.preventDefault();
            }else{
              e.returnValue = false;
              return false;           //做額外的預防措施,返回 false
            }
          
參考 MSDN return 陳述式:https://msdn.microsoft.com/zh-tw/library/22a685h9(v=vs.94).aspx
參考 MDN Event.stopPropagation():https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation

事件的兩個階段


事件經歷的兩階段:捕捉(Capturing)、冒泡(Bubbling)
以下面例子來說明,當滑鼠停在連結上方時,實際上經歷了幾個步驟,在兩個不同階段內達到和離開目標(連結)。
            

This is a title.

This is a link.

            addEvent(U.$('link'), 'mouseover', doSomething);
          
執行上術程式碼,連結在段落之內,段落在 DIV 之內,DIV 又在 HTML 主體之內,HTML 主體屬於文件(Document),文件位於視窗之中: 這兩者行為的作用是為了捕獲該目標,啟動該事件,假設以上例子的事件處理器註冊在 div,當游標進入 div 就會發生 mouseover 事件,但是一旦游標進入 h1、p 或 a 時,div 不再是活動元素,就不能當作 mouseover 事件的目標,不過藉由冒泡事件之助,仍可以捕獲該事件:當游標停在 h1、p 或 a 時,事件開始冒泡,最終被 div 的事件處理器捕捉。

阻止事件冒泡


阻止事件冒泡是影響事件運作的方法之一,是在事件發生時阻止瀏覽器的預設行為,事件處理器的另一個作用是阻止事件冒泡,根據瀏覽器是否支援 addEventListener() 方法:
            if(e.stopPropogation){	    //W3C addEventListener
            	e.stopPropogation();
            }else{                	    //IE8 以下
            	e.cancelBubble = true;
            }
          
讓函數返回 false 值也能阻止冒泡,此法副作用可能不符預期,且並不經常需要取消事件冒泡。

預設情況下,所有事件監聽僅關注冒泡階段,在非 IE 瀏覽器上,addEventListener() 方法的第三參數提供 true 值,以便捕捉階段監視事件,實際上,指定事件處理器的傳統方法只能在冒泡階段監視事件(attachEvent() 方法未提供表示該階段的參數):
            addEventlistener(someDiv, 'mouseover', true);
          
因 IE8 以前的版本無法正常運作,所以事件捕捉不常用,在支援的瀏覽器上,捕捉階段可以攔截事件,或為將要發生在另一元素上方的事件準備瀏覽器。
focus、blur、change、scroll 和 submit 等事件沒有冒泡階段。

定義事件處理器時,應考慮監視的元素是否具有子元素,尤其是監視 mouseover 和 mouseout 事件時,可利用 relatedTarget 屬性來保留滑鼠來源元素(mouseover)或去向元素(mouseout),而微軟的解決方法是位同一目的建立兩個屬性:fromElementtoElement
參考 JavaScript Tutorial Bubbling and capturing:http://javascript.info/tutorial/bubbling-and-capturing
參考 MDN Event.stopPropagation():https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation
參考 MSDN cancelBubble property:https://msdn.microsoft.com/zh-tw/library/ms533545(v=vs.85).aspx

委託事件處理


理解事件階段之後,就能利用兩種方式應用事件處理:1.將事件監聽器繫結到具體元素(到目前為止都使用此法),2.執行事件委派(Delegation):將事件處理器連接到父元素,再捕捉從子元素冒泡的任何事件,在多個元素擁有相同事件處理器的情況下,委派能夠提供漸進式改善的性能和簡潔的程式碼 參考 W3schools jQuery delegate() Method:http://www.w3schools.com/jquery/event_delegate.asp
參考 jQuery .delegate():http://api.jquery.com/delegate/
DEMO MEMO