web交互之js事件

前言

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

1、事件流

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

一、事件冒泡

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

web交互之js事件

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

二、事件捕獲

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

web交互之js事件

三、DOM事件流

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

web交互之js事件

IE九、Opera、Firefox、Chrome和Safari都支持DOM事件流;IE8及更早版本不支持DOM事件流。服務器

2、事件處理程序

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

一、HTML事件處理程序

在HTML中定義的事件處理程序能夠包含要執行的具體動做,也能夠調用在頁面其餘地方定義的腳本。編輯器

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

或者ide

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

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

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

二、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;

三、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。大多數狀況下,都是將事件處理程序添加到事件流的冒泡階段,這樣能夠最大限度地兼容各類瀏覽器。IE九、Opera、Firefox、Chrome和Safari都支持DOM2級事件處理程序。

四、IE事件處理程序

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

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

注意:一、事件名前面有on前綴;二、在事件處理程序的函數中,this再也不指向當前元素,而是指向window對象。

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

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

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

第一個要建立的方法是 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);

六、事件回調函數的做用域問題

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

(1)使用匿名函數

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

(2)使用 bind 方法

使用匿名函數是有缺陷的,每次調用都包裹進匿名函數裏面,增長了冗餘代碼等,此外若是想使用 removeEventListener 解除綁定,還須要再建立一個函數引用。Function 類型提供了 bind 方法,能夠爲函數綁定做用域,不管函數在哪裏調用,都不會改變它的做用域。

3、事件對象

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

一、DOM中的事件對象

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

web交互之js事件

web交互之js事件

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

二、IE中的事件對象

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

web交互之js事件

三、跨瀏覽器的事件對象

綜合考慮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;    
        }    
    }    
};

4、事件類型

Web瀏覽器中可能發生的事件有不少類型。如前所述,不一樣的事件類型具備不一樣的信息,而 "DOM3級事件" 規定了下列幾類事件:
UI事件:在用戶與頁面上的元素交互時觸發;
鼠標事件:當用戶經過鼠標在頁面上執行操做時觸發;
滾輪事件:當使用鼠標滾輪(或相似設備)時觸發;
文本事件:當在文檔中輸入文本時觸發;
鍵盤事件:當用戶經過鍵盤在頁面上執行操做時觸發;
合成事件:當爲IME(Input Method Editor,輸入法編輯器)輸入字符時觸發;

變更 (mutation) 事件:當底層 DOM 結構發生變化時觸發。

一、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事件

二、焦點事件

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

三、鼠標與滾輪事件

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屬性。

四、鍵盤與文本事件

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

五、複合事件:用以處理IME的輸入序列的一類事件。 六、變更事件:刪除節點、插入節點等。 七、HTML5事件

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

八、設備事件

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

九、觸摸與手勢事件

1.觸摸事件

2.手勢事件

5、內存和性能

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替換的元素,而是將事件賦給其父元素,這樣就能夠避免了。卸載頁面時,最好手工清除全部的事件處理程序。

6、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" ] );
});
相關文章
相關標籤/搜索