js 事件常用方法

一、介紹

    事件是用來實現js和html之間交互的,可以用偵聽器(或處理程序)來預訂事件,以便事件發生時執行相應的代碼。這種在傳統軟件工程中被稱爲觀察員模式的模型,支持頁面的行爲(js)與頁面的外觀(html和css)的鬆散耦合。事件最早是在IE3和Netscape Navigator2中出現的,當時是作爲分擔服務器運算負載的一種手段。

二、事件流

    事件流描述的是從頁面中接收事件的順序。IE的事件流是事件冒泡流,而Netscape Communicator 的事件流是事件捕獲流。

1、事件冒泡
    IE 的事件流叫做事件冒泡,即事件開始時由最具體的元素 (文檔中嵌套層次最深的那個節點) 接收,然後逐級向上傳播到較爲不具體的節點(文檔)。比如單擊了頁面中的<div>元素,那麼這個click事件會按照如下順序逐級向上傳播,直至傳播到 document 對象。

    注意:所有現代瀏覽器都支持事件冒泡,但在具體實現上還是有一些差別。IE5.5及更早版本中的事件冒泡會跳過 <html> 元素 (從 <body> 直接跳到 document) 。Firefox、Chrome 和 Safari 則將事件一直冒泡到 window 對象。

2、事件捕獲

    事件捕獲的思想是不太具體的節點應該更早接收到事件,而最具體的節點應該最後接收到事件。事件捕獲的用意在於在事件到達預定目標之前捕獲它。前面的例子的事件捕獲過程爲:

3、DOM事件流

    “DOM2級事件”規定的事件流包括三個階段:事件捕獲階段、處於目標階段和事件冒泡階段。同樣前面的例子觸發事件的順序爲:

 IE9、Opera、Firefox、Chrome和Safari都支持DOM事件流;IE8及更早版本不支持DOM事件流。

三、事件處理程序

    事件就是用戶或瀏覽器自身執行的某種動作。而響應某個事件的函數就叫做事件處理程序 (或事件偵聽器)。事件處理程序的名字以 "on" 開頭,比如 click 事件的事件處理程序就是 onclick。爲事件指定處理程序的方式有以下幾種。

1、HTML事件處理程序
    在HTML中定義的事件處理程序可以包含要執行的具體動作,也可以調用在頁面其他地方定義的腳本。

<input type="button" value="Click Me" οnclick="alert(this.value)" />  

或者

<script type="text/javascript">    
  function showMessage(){    
    alert("Hello world!");    
  }    
</script>    
<input type="button" value="Click Me" οnclick="showMessage()" />  

注意:不能再其中使用未經轉義的HTML語法字符,例如和號(&)、雙引號("")、小於號(<)或大於號(>)。

    在HTML中指定事件處理程序有三個缺點。1、存在一個時差問題。因爲用戶可能會在HTML元素一出現在頁面上就觸發相應的事件,但當時的事件處理程序有可能尚不具備執行條件。爲此,很多HTML事件處理程序都會被封裝在一個try-catch塊中,以便錯誤不會浮出水面。2、這樣擴展事件處理程序的作用域在不同瀏覽器中會導致不同結果。不同js引擎遵循的標識符解析規則略有差異,很可能會在訪問非限定對象成員時出錯。3、html與js代碼緊密耦合,更換事件處理程序改動較大。

2、DOM0級事件處理程序

    通過 JavaScript 指定事件處理程序的傳統方式,就是將一個函數賦值給一個事件處理程序屬性。這種爲事件處理程序賦值的方法是在第四代Web瀏覽器中出現的,而且至今仍然爲所有現代瀏覽器所支持。原因一是簡單,二是具有跨瀏覽器的優勢。

    每個元素都有自己的事件處理程序屬性,如onclick等。可以通過js將一個函數賦值給元素的事件處理程序屬性。在DOM0事件處理程序中,事件處理程序裏的this指向當前元素。

var objlogo=document.getElementById("site_nav_top");
objlogo.οnclick=function(){
alert(this.innerHTML);//代碼改變世界
}

刪除DOM0事件,只需將事件處理程序的值賦爲null即可。

objlogo.οnclick=null;

3、DOM2級事件處理程序

“DOM2級事件”定義了兩個方法,addEventListener() 和 removeEventListener(),它們都接受3個參數:要處理的事件名、作爲事件處理程序的函數和一個布爾值。最後這個布爾值參數如果是 true,表示在捕獲階段調用事件處理程序;如果是 false,表示在冒泡階段調用事件處理程序。addEventListener()可以爲元素添加多個事件處理程序,觸發時會按照添加順序依次調用。removeEventListener()不能移除匿名添加的函數。 

var btn = document.getElementById("myBtn");  
btn.addEventListener("click", function(){  
alert(this.id);  
}, false);  
var handler = function(){  
alert("Hello world!");  
};  
btn.addEventListener("click", handler, false);  
// 這裏省略了其他代碼  
btn.removeEventListener("click", handler, false); // 有效!

以上代碼會先顯示id,再顯示hello world。大多數情況下,都是將事件處理程序添加到事件流的冒泡階段,這樣可以最大限度地兼容各種瀏覽器。IE9、Opera、Firefox、Chrome和Safari都支持DOM2級事件處理程序。

4、IE事件處理程序

IE 實現了與DOM中類似的兩個方法:attachEvent() 和 detachEvent()。這兩個方法接受相同的兩個參數:事件處理程序名稱與事件處理程序函數。由於IE只支持事件冒泡,所以通過 attachEvent() 添加的事件處理程序都會被添加到冒泡階段。

    在使用DOM0級方法的情況下,事件處理程序會在其所屬元素的作用域內運行;在使用 attachEvent() 方法的情況下,事件處理程序會在全局作用域中運行,因此 this 等於 window 。並且,與DOM 方法不同的是,這些事件處理程序不是以添加它們的順序執行,而是以相反的順序被觸發。    

var btn = document.getElementById("myBtn");  
var handler = function(){  
alert("Clicked");  
};  
btn.attachEvent("onclick", handler);  
// 這裏省略了其他代碼  
btn.detachEvent("onclick", handler); 

支持IE事件處理程序的瀏覽器有IE和Opera。

5、跨瀏覽器的事件處理程序

第一個要創建的方法是 addHandler(),它的職責是視情況分別使用DOM0級方法、DOM2級方法或IE方法來添加事件,它屬於一個名叫 EventUtil 的對象。addHandler()方法接受3個參數:要操作的元素、事件名稱和事件處理程序函數。EventUtil的用法如下所示:

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;    
        }    
    }    
}; 

使用EventUtil對象的實例如下:

var btn = document.getElementById("myBtn");  
var handler = function(){  
    alert("Clicked");  
};  
EventUtil.addHandler(btn, "click", handler);  
// 這裏省略了其他代碼  
EventUtil.removeHandler(btn, "click", handler); 

6、事件回調函數的作用域問題

    事件綁定函數時,該函數會以當前元素爲作用域執行。

(1)使用匿名函數

    我們爲回調函數包裹一層匿名函數。包裹之後,雖然匿名函數的作用域被指向事件觸發元素,但執行的內容就像直接調用一樣,不會影響其作用域。

(2)使用 bind 方法

    使用匿名函數是有缺陷的,每次調用都包裹進匿名函數裏面,增加了冗餘代碼等,此外如果想使用 removeEventListener 解除綁定,還需要再創建一個函數引用。Function 類型提供了 bind 方法,可以爲函數綁定作用域,無論函數在哪裏調用,都不會改變它的作用域。

四、事件對象

在觸發 DOM 上的某個事件時,會產生一個事件對象 event,這個對象中包含着所有與事件有關的信息。

1、DOM中的事件對象

 兼容 DOM 的瀏覽器會將一個 event 對象傳入到事件處理程序中。event 對象包含與創建它的特定事件有關的屬性和方法。觸發的事件類型不一樣,可用的屬性和方法也不一樣。不過,所有事件都會有下表列出的成員。

在事件處理程序內部,對象 this 始終等於 currentTarget 的值,而 target 則只包含事件的實際目標。如果直接將事件處理程序指定給了目標元素,則 this 、currentTarget 和 target 包含相同的值。

2、IE中的事件對象

在使用 DOM0 級方法添加事件處理程序時,event 對象作爲 window 對象的一個屬性存在。如果是通過 HTML 特性指定的事件處理程序,那麼還可以通過一個名叫 event 的變量來訪問 event 對象 (與 DOM 中的事件模型相同)。IE 的 event 對象同樣也包含與創建它的事件相關的屬性和方法。

3、跨瀏覽器的事件對象

綜合考慮DOM和IE中的事件對象,寫出跨瀏覽器的事件對象,放在之前的EventUtil中。

var EventUtil = {    
    addHandler: function(element, type, handler){    
        // 省略了其他代碼    
    },    
    getEvent: function(event){    
        return event? event: window.event;    
    },    
    getTarget: function(event){    
        return event.target || event.srcElement;    
    },    
    preventDefault: function(event){    
        if (event.preventDefault){    
            event.preventDefault();    
        } else {    
            event.returnValue = false;    
        }    
    },    
    removeHandler: function(element, type, handler){    
        // 省略了其他代碼    
    },    
    stopPropagation: function(event){    
        if (event.stopPropagation){    
            event.stopPropagation();    
        } else {    
            event.cancelBubble = true;    
        }    
    }    
}; 

四、事件類型

Web瀏覽器中可能發生的事件有很多類型。如前所述,不同的事件類型具有不同的信息,而 "DOM3級事件" 規定了下列幾類事件:    

    UI事件:在用戶與頁面上的元素交互時觸發;
    鼠標事件:當用戶通過鼠標在頁面上執行操作時觸發;
    滾輪事件:當使用鼠標滾輪(或類似設備)時觸發;
    文本事件:當在文檔中輸入文本時觸發;
    鍵盤事件:當用戶通過鍵盤在頁面上執行操作時觸發;
    合成事件:當爲IME(Input Method Editor,輸入法編輯器)輸入字符時觸發;
    變動 (mutation) 事件:當底層 DOM 結構發生變化時觸發。

1、UI事件   

    (1)load事件:load可以用來判斷圖片加載完畢。注意:新創建的圖像元素不是在加載到頁面中才開始下載,而是設置src之後就開始下載。load可以用來判斷js加載完成。<script>元素可以觸發load事件,來判斷動態加載的js文件是否加載完成。它和img不同,必須設置了src屬性並且添加到文檔之後纔會開始下載。

    (2).unload事件

    (3).resize事件
    注意:關於何時會觸發resize事件,不同瀏覽器有不同的機制。IE、Safari、Chrome和Opera會在瀏覽器窗口變化了1像素時就觸發resize事件,然後隨着變化不斷重複觸發。Firefox則只會在用戶停止調整窗口大小時纔會觸發resize事件。
    (4).scroll事件

2、焦點事件

焦點事件會在頁面元素獲得或失去焦點時觸發,利用這些事件並與document.hasFocus()方法及document.activeElement屬性配合,可以知曉用戶在頁面上的行蹤。有以下6個焦點事件:blur、DOMFocusIn、DOMFocusOut、foucs、focusin、focusout。

3、鼠標與滾輪事件

DOM3級事件定義了9個鼠標事件:click、dblclick、mousedown、mouseenter、mouseleave、mousemove、mouseout、mouseover、mouseup。
    鼠標事件中還有一類滾輪事件mousewheel事件。
    (1).客戶區座標位置:clientX和clientY屬性。
    (2).頁面座標位置:pageX和pageY屬性。
    (3).屏幕座標位置:screenX和screenY屬性。
    (4).修改鍵:shiftKey、ctrlKey、altKey、metaKey。
    (5).相關元素:DOM通過event對象的relatedTarget屬性提供了相關元素的信息。而在IE中,mouseover事件觸發時,fromElement保存了相關元素,mouseout事件觸發時,toElement保存了相關元素。
    (6).鼠標按鈕
    (7).更多的事件信息

    (8).鼠標滾輪事件:mousewheel事件中包含wheelDelta屬性,Firefox支持類似的DOMMouseScroll事件,包含detail屬性。

4、鍵盤與文本事件

DOM3級含有3個鍵盤事件:keydown、keypress、keyup。
    1.鍵碼:keyCode屬性
    2.字符編碼:charCode屬性
    3.DOM3級變化:新屬性(key、char、location屬性:表示按下了什麼位置上的鍵),getModifierState()方法。
    4.textinput事件:data屬性:表示用戶輸入的字符(而非字符編碼)、inputMethod屬性:表示把文本輸入到文本框中的方式。

5、複合事件:用以處理IME的輸入序列的一類事件。
6、變動事件:刪除節點、插入節點等。
7、HTML5事件

    1.contextmenu事件:用以表示何時應該顯示上下文菜單。
    2.beforeunload事件:讓開發人員有可能在頁面卸載前阻止這一操作。
    3.DOMContentLoaded事件:在形成完整的DOM樹之後觸發。
    4.readystatechange事件:提供與文檔或元素的加載狀態有關的信息。
    5.pageshow和pagehide事件:pageshow事件在頁面顯示時觸發,pagehide事件在瀏覽器卸載頁面時觸發。
    6.hashchange事件:以便在URL的參數列表發生變化時通知開發人員。

8、設備事件

    1.orientationchange事件
    2.MozOrientation事件
    3.deviceorientation事件
    4.devicemotion事件

9、觸摸與手勢事件

    1.觸摸事件

    2.手勢事件

五、內存和性能

1.事件委託

事件委託可以解決頁面中事件處理程序過多的問題。事件委託利用了事件冒泡,只指定一個事件處理程序,就可以處理某一類型的所有事件。

EventUtil.addHandler(document.body, 'click', function (event) {
  event = EventUtil.getEvent(event);
  var target = EventUtil.getTarget(event);
  switch (target.id) {
  case 'site_nav_top':
    alert('口號');
    break;
  case 'nav_menu':
    alert('點擊菜單');
    EventUtil.preventDefault(event);
    break;
  case 'editor_pick_lnk':
    alert('推薦區');
    EventUtil.preventDefault(event);
    break;
  }
});

2.移除事件處理程序

如果內存中保留大量無用的事件處理程序,會影響性能。所以一定要在不需要的時候及時移除事件處理程序。尤其注意以下情況:使用innerHTML刪除帶有事件處理程序的元素時,要先將事件處理程序設置爲null。使用委託也可以解決這個問題,不直接將事件加載會被innerHTML替換的元素,而是將事件賦給其父元素,這樣就可以避免了。卸載頁面時,最好手工清除所有的事件處理程序。

六、DOM中的事件模擬

 事件經常由用戶操作或通過其他瀏覽器功能來觸發,也可以使用JS在任意時刻來觸發特定的事件,而此時的事件就如同瀏覽器創建的一樣。在測試Web應用程序,模擬觸發事件是一種極其有用的技術。

1. DOM中的事件

模擬分三步:

    使用document.createEvent()創建event對象。通過賦值不同的參數,可以模擬不同的事件類型
    初始化event對象;

    觸發事件,調用dispatchEvent()方法,所有支持事件的DOM節點都可以支持這個方法。

function simulateClick() {
  var evt = document.createEvent("MouseEvents");
  evt.initMouseEvent("click", true, true, window,
    0, 0, 0, 0, 0, false, false, false, false, 0, null);
  var cb = document.getElementById("checkbox"); 
  var canceled = !cb.dispatchEvent(evt);
  if(canceled) {
    // A handler called preventDefault
    alert("canceled");
  } else {
    // None of the handlers called preventDefault
    alert("not canceled");
  }
}

2. 模擬鼠標事件

首先createEvent()方法傳入參數是“MouseEvent”來模擬鼠標事件。返回的event對象有一個initMouseEvent()方法,用來初始化事件信息。該方法有15個參數:
    type(字符串):要觸發的事件類型,比如“click”。
    bubbles(bool):事件是否冒泡。一般設爲true。
    cancelable(bool):事件是否可以取消。一般設爲true。
    view:與事件關聯的視圖,一般設置爲document.defaultView。
    detail(整數):與事件有關的詳細信息,一般設置爲0.
    screenX:事件相對於屏幕的X座標。
    screenY:事件相對於屏幕的Y座標。
    clientX:事件相對於視口的X座標。
    clientY:事件相對於視口的Y座標。
    ctrlKey(bool):是否俺下了Ctrl鍵,默認爲false。
    altKey:是否按下了alt鍵,默認false。
    shiftKey:是否按下了shift鍵,默認false。
    metaKey:是否按下了meta鍵,默認false.
    button(整數):按下了哪個鼠標鍵,默認0。
    relatedTarget:與事件相關的對象,一般爲null.只在模擬mouseover和mouseout的時候會用到。
    最後給DOM元素調用dispatchEvent()方法來觸發事件。

var objmenu = document.getElementById('nav_menu');
objmenu.onclick = function () {
  console.log('menu');
}
document.body.onclick = function () {
  console.log('body');
}
var event = document.createEvent('MouseEvent');
event.initMouseEvent('click', true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
for (var i = 0; i < 5; i++) {
  objmenu.dispatchEvent(event);
}

3. 自定義DOM事件

 DOM3中支持自定義DOM事件。要創建新的自定義事件,可以調用document.createEvent()方法,返回的對象有一個initCustomEvent()方法,包含四個參數:
    type:觸發事件的類型;
    bubbles:事件是否冒泡;
    cancelable:事件是否可以取消;
    detail:任意值,保存在event.detail屬性中。
    最後在DOM元素調用dispatchEvent()方法觸發事件。

var objmenu = document.getElementById('nav_menu');
EventUtil.addHandler(objmenu, 'myevent', function (event) {
  console.log('menu' + event.detail);
});
EventUtil.addHandler(document.body, 'myevent', function (event) {
  console.log('body' + event.detail);
})
if (document.implementation.hasFeature('CustomEvents', '3.0')) {
  var event = document.createEvent('CustomEvent');
  event.initCustomEvent('myevent', true, true, '測試事件detail');
  for (var i = 0; i < 5; i++) {
    objmenu.dispatchEvent(event);
  }
}

自定義事件的函數有 Event、CustomEvent 和 dispatchEvent。直接自定義事件,使用 Event 構造函數;CustomEvent 可以創建一個更高度自定義事件,還可以附帶一些數據,具體用法如下:

var myEvent = new CustomEvent(eventname, options);

其中 options 可以是:

{
    detail: {
        ...
    },
    bubbles: true,
    cancelable: false
}

其中 detail 可以存放一些初始化的信息,可以在觸發的時候調用。其他屬性就是定義該事件是否具有冒泡等等功能。內置的事件會由瀏覽器根據某些操作進行觸發,自定義的事件就需要人工觸發。dispatchEvent 函數就是用來觸發某個事件:

element.dispatchEvent(customEvent);

上面代碼表示,在 element 上面觸發 customEvent 這個事件。結合起來用就是:

// add an appropriate event listener
obj.addEventListener("cat", function(e) { process(e.detail) });
 
// create and dispatch the event
var event = new CustomEvent("cat", {"detail":{"hazcheeseburger":true}});
obj.dispatchEvent(event);

使用自定義事件需要注意兼容性問題,而使用 jQuery 就簡單多了:

// 綁定自定義事件
$(element).on('myCustomEvent', function(){});
 
// 觸發事件
$(element).trigger('myCustomEvent');

此外,你還可以在觸發自定義事件時傳遞更多參數信息:

$( "p" ).on( "myCustomEvent", function( event, myName ) {
  $( this ).text( myName + ", hi there!" );
});
$( "button" ).click(function () {
  $( "p" ).trigger( "myCustomEvent", [ "John" ] );
});

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章