本章內容:javascript
事件流描述的是從頁面中接受事件的順序。但有意思的是,IE和Netscape開發團隊竟然提出了差很少徹底相反的事件流的概念。IE的事件流是事件冒泡流,而Netscape Communicator的事件流是事件捕獲流。html
即事件開始時由最具體的元素接收,而後逐級向上傳播到較爲不具體的節點。java
全部現代瀏覽器都支持事件冒泡,但在具體實現上仍是有一些差異。IE5.5及更早版本中的事件冒泡會跳過<html>
元素(從<body>
直接跳到document
)。IE九、Firefox、Chrome和Safari則將事件一致冒泡到window對象。android
事件捕獲的思想是不太具體的節點應該更早接收到事件,而最具體的節點應該最後接收到事件。ios
DOM2級事件規定的事件流包括三個階段:事件捕獲階段、處於目標階段和事件冒泡階段。首先發生的是事件捕獲,爲截獲事件提供了機會。而後是實際的目標接收到事件。最後一個階段是冒泡階段,能夠在這個階段對事件作出響應。
多數支持DOM事件流的瀏覽器都實現了一種特定的行爲;即便「DOM2級事件」規範明確要求捕獲階段不會涉及事件目標,但IE九、Safari、Firefox和Opera9.5及更高版本都會在捕獲階段觸發事件對象上的事件。結果,就是有兩個機會在目標對象上面操做事件。git
事件就是用戶或瀏覽器自身執行的某種動做。諸如click、load和mouseover,都是事件的名字。而響應某個事件的函數就是事件處理程序。事件處理程序的名字以「on」開頭,所以click事件的事件處理程序就是onclick,load事件的事件處理程序就是onload。爲事件指定處理程序的方式有好幾種。github
<script type='text/javascript'>
function showMessage() {
alert('hello world');
}
</script>
<input type='button' value='Click Me' onclick='showMessage()' />
複製代碼
在這個例子中,單擊按鈕就會調用showMessage()函數。這個函數是在一個獨立的<script>
元素中定義的,固然也能夠被包含在一個外部文件中。事件處理程序中的代碼在執行時,有權訪問全局做用域中的任何代碼。web
這樣指定事件處理程序具備一些獨到之處。首先,這樣會建立一個封裝着元素屬性值的函數。這個函數中有一個局部變量event,也就是事件對象:api
<input type='button' value="Click Me" onClick='alert(event.type)'>
複製代碼
經過event變量,能夠直接訪問事件對象,你不用本身定義它,也不用從函數的參數列表中讀取。在這個函數內部,this值等於事件的目標元素。數組
<input type='button' value='Click Me' onclick='alert(this.value)'>
複製代碼
關於這個動態建立的函數,另外一個有意思的地方是它擴展做用域的方式。在這個函數內部,能夠像訪問局部變量同樣訪問document及該元素自己的成員。這個函數使用with像下面這樣擴展做用域:
function () {
with(document) {
with(this) {
// 元素屬性值
}
}
}
複製代碼
如此一來,事件處理程序要訪問本身的屬性就簡單多了。下面這行代碼與前面的例子效果相同:
<input type='button' value='Click Me' onclick='alert(value)'>
複製代碼
若是當前元素是一個表單輸入元素,則做用域中還會包含訪問表單元素的入口,這個函數就變成了以下所示:
<form method='post'>
<input type='text' name='username' value=''>
<input type='button' value='Echo Username' onclick='alert(username.value)'>
</form>
複製代碼
在這個例子中,單擊按鈕會顯示文本框中的文本。值得注意的是,這裏直接引用了username元素。
不過,在HTML中指定事件處理程序有兩個缺點。
<input type='button' value='Click Me' onclick='showMessage()' />
<script type='text/javascript'>
function showMessage() {
alert('hello world');
}
</script>
複製代碼
若是用戶在頁面解析showMessage()函數以前就單擊了按鈕,就會引起錯誤。爲此,不少HTML事件處理程序都會被封裝在一個try-catch塊中,以便錯誤不會浮出水面。
<input type='button' value='Click Me' onclick='try{showMessage()}catch(ex){}'>
複製代碼
另外一個缺點就是,這樣擴展事件處理程序的做用域鏈在不一樣瀏覽器中會致使不一樣結果。不一樣Javascript引擎遵循的標識符解析規則略有差別,極可能會在訪問非限定對象成員時出錯。
最後一個缺點是HTML與Javascript代碼緊密耦合。若是要更換事件處理程序,就要改動兩個地方:HTML代碼和Javascript代碼。
經過JavaScript指定事件處理程序的傳統方式,就是講一個函數賦值給一個事件處理程序屬性。這種爲事件處理程序賦值的方法是在第四代Web瀏覽器中出現的,並且至今仍然爲全部現代瀏覽器所支持。緣由:
每一個元素都有本身的事件處理程序屬性,這些屬性一般所有小寫,例如onclick。將這種屬性的值設置爲一個函數,就能夠指定事件處理程序:
var btn = document.getElementById('myBtn');
btn.onclick = function () {
alert('Clicked');
}
複製代碼
有一些缺點:
在這些代碼運行之前不會指定事件處理程序,所以若是這些代碼在頁面中位於按鈕後面,就有可能在一段時間內怎麼單擊都沒反應。
使用DOM0級方法指定的事件處理程序被認爲是元素的方法。所以,這時候的事件處理程序是在元素的做用域中運行;換句話說,程序中的this引用當前元素。
<input type='button' id='myBtn'>
var btn = document.getElementById('myBtn');
btn.onclick = function () {
alert(this.id);
}
複製代碼
單擊按鈕顯示的是元素的ID,這個ID是經過this.id取得的。不只僅是ID,實際上能夠在事件處理程序中經過this訪問元素的任何屬性和方法。以這種方式添加的事件處理程序會在事件流的冒泡階段被處理。
也能夠刪除經過DOM0級方法指定的事件處理程序,只要像下面這樣將事件處理程序屬性的值設置爲null便可:
btn.onclick = null;
複製代碼
「DOM2級事件」定義了兩個方法,用於處理指定和刪除事件處理程序的操做:addEventListener()和removeEventListener()。全部DOM節點中都包含這兩個方法,而且它們都接收3個參數:要處理的事件名、做爲事件處理程序的函數和一個布爾值。最後這個布爾值參數若是是true,表示在捕獲階段調用事件處理程序;若是是false,表示在冒泡階段調用事件處理程序。
要在按鈕上爲click事件添加事件處理程序,可使用下列代碼:
var btn = document.getElementById('myBtn');
btn.addEventListener('click', function () {
alert(this.id);
}, false);
複製代碼
上面的代碼爲一個按鈕添加了onclick事件處理程序,並且該事件會在冒泡階段被觸發。與DOM0級方法同樣,這裏添加的事件處理程序也是在其依附的元素的做用域中運行。使用DOM2級方法添加事件處理程序的好處是能夠添加多個事件處理程序。來看下面的例子。
var btn = document.getElementById('MyBtn');
btn.addEventListener('click', function () {
alert('Hello world');
}, false);
複製代碼
經過addEventListener()添加的事件處理程序只能使用removeEventListener()來移除;移除時傳入的參數與添加處理程序時使用的參數相同。這也意味着經過addEventListener()添加的匿名函數將沒法移除:
var btn = document.getElementById('myBtn');
btn.addEventListener('click', function () {
alert(this.id);
}, false)
// 這裏省略了其餘代碼
btn.removeEventListener('click', function () {
alert(this.id);
}, false) // 這樣無效
var handler = function () {
alert(this.id);
}
btn.addEventListener('click', handler, false)
btn.removeEventListener('click', handler, false) // 這樣有效
複製代碼
大多數狀況下,都是將事件處理程序添加到事件流的冒泡階段,這樣能夠最大限度地兼容各類瀏覽器。最好只在須要在事件到達目標以前截獲它的時候將事件處理程序添加到捕獲階段。若是不是特別須要,咱們不建議在事件捕獲階段註冊事件處理程序。
IE實現了與DOM中相似的兩個方法:attachEvent()和detachEvent()。這兩個方法接收相同的兩個參數:事件處理程序與事件處理程序函數。因爲IE8及更早版本只支持事件冒泡,因此經過attachEvent()添加的事件處理程序都會被添加到冒泡階段。
要使用attachEvent()爲按鈕添加一個事件處理程序,可使用如下代碼。
var btn = docunment.getElementById('myBtn');
btn.attachEvent('onclick', function () {
alert('Clicked');
})
複製代碼
在IE中使用attachEvent()與使用DOM0級方法的主要區別在於事件處理程序的做用域。在使用DOM0級方法的狀況下,事件處理程序會在全局做用域運行,所以this等於window。
來看下面的例子。
var btn = document.getElementById('myBtn');
btn.attachEvent('onclick', function () {
alert(this=== window); // true
})
複製代碼
在編寫跨瀏覽器的代碼時,牢記這一區別很是重要。
使用attachEvent()添加的事件能夠經過detachEvent()來移除,條件是必須提供相同的參數。與DOM方法同樣,這也意味着添加的匿名函數將不能被移除。不過,只要可以將對相同函數的引用傳給dtachEvent(),就能夠移除相應的事件處理程序。
爲了以跨瀏覽器的方式處理事件,很多開發人員會使用可以隔離瀏覽器差別的JavaScript庫。
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.detach('on'+ type, handler);
} else {
element['on' + type] = null;
}
}
}
複製代碼
在觸發DOM上的某個事件時,會產生一個事件對象event,這個對象中包含着全部與事件有關的信息。包括致使事件的元素、事件的類型以及其餘與特定事件相關的信息。
兼容DOM的瀏覽器會將一個event對象傳入到事件處理程序中。不管指定事件處理程序時使用什麼方法(DOM0級或DOM2級),都會傳入event對象。
var btn = document.getElementById('myBtn');
btn.onclick = function (event) {
alert(event.type) //'click'
}
btn.addEventListener('click', function (event) {
alert(event.type) //'click'
}, false);
複製代碼
這個例子中的兩個事件處理程序都會彈出一個警告框,顯示由event.type屬性表示的事件類型。這個屬性始終都會包含被觸發的事件類型。
event對象包含與建立它的特定事件有關的屬性和方法。觸發的事件類型不同,可用的屬性和方法也不同。不過,全部事件都會有下表列出的成員。
屬性/方法 | 類型 | 讀/寫 | 說明 |
---|---|---|---|
preventDefault() | Function | 只讀 | 取消事件的默認行爲。若是cancelable是true,則可使用這個方法 |
stopImmediatePropagation() | Function | 只讀 | 取消事件的進一步捕獲或冒泡。同時阻止任何事件處理程序被調用(DOM3級事件中新增) |
stopPropagation() | Function | 只讀 | 取消事件的進一步捕獲或冒泡。若是bubbles爲true,則可使用這個方法 |
target | Element | 只讀 | 事件的目標 |
trusted | Boolean | 只讀 | 爲true表示事件是瀏覽器生成的。爲false表示事件是由開發人員經過Javascript建立的(DOM3級事件中新增) |
type | String | 只讀 | 被觸發的事件的類型 |
view | AbstractView | 只讀 | 與事件關聯的抽象視圖。等同於發生事件的window對象 |
看下面例子:
document.body.onclick = function (event) {
alert(event.currentTarget === document.body); // true
alert(this === document.body) // true
alert(event.target === document.getElementById('myBtn')); // true
};
複製代碼
當單擊這個例子中的按鈕時,this和currentTarget都等於document.body,由於事件處理程序是註冊到這個元素上的。然而,target元素卻等於按鈕元素,由於它是click事件真正的目標。因爲按鈕上並無註冊事件處理程序,結果click事件就冒泡到document.body,在那裏事件才獲得了處理。
在須要經過一個函數處理多個事件時,可使用type屬性。例如:
var btn = document.getElementById('myBtn');
var handler = function (event) {
switch (event.type) {
case 'click':
alert('Clicked');
break;
case 'mouseover':
event.target.style.backgroundColor = 'red';
break;
case 'mouseout':
event.target.style.backgroundColor = '';
break;
}
};
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;
複製代碼
要阻止特定事件的默認行爲,可使用preventDefault()方法。例如,連接的默認行爲就是在被單擊時會導航到其href特性指定的URL。若是你想阻止連接導航這一默認行爲,那麼經過連接onclick事件處理程序能夠取消它。
只有cancelable屬性設置爲true的事件,纔可使用preventDefault()來取消其默認行爲。
另外,stopPropagation()方法用於當即中止事件在DOM層次中的傳播,即取消進一步的事件捕獲或冒泡。例如,直接添加到一個按鈕的事件處理程序能夠調用stopPropagation(),從而避免觸發註冊在document.body上面的事件處理程序,以下面的例子所示。
事件對象的eventPhase屬性,能夠用來肯定事件當前正位於事件流的哪一個階段。若是是在捕獲階段調用的事件處理程序,eventPhase等於1;若是事件處理程序處於目標對象上,則eventPhase等於2;若是在冒泡階段調用的事件處理程序,eventPhase等於3。
注意,儘管「處於目標」發生在冒泡階段,但eventPhase仍然一直等於2。
當eventPhase等於2時,this、target和currentTarget始終是相等的。
只有在事件處理程序執行期間,event對象纔會存在;一旦事件處理程序執行完成,event對象就會被銷燬。
與訪問DOM中的event對象不一樣,要訪問IE中的event對象有幾種不一樣的方式,取決於指定事件處理程序的方法。在使用DOM0級方法添加事件處理程序時,event對象做爲window對象的一個屬性存在。來看下面的例子。
var btn = document.getElementById('myBtn');
btn.onclick = function () {
var event = window.event;
alert(event.type) //'click'
}
複製代碼
但是,若是事件處理程序是使用attachEvent()添加的,那麼就會有一個event對象做爲參數被傳入事件處理程序函數中,以下所示。
var btn = document.getElementById('myBtn');
btn.attacehEvent('onclick', function (event) {
alert(event.type); //'click'
})
複製代碼
在像這樣使用attachEvent()狀況下,也能夠經過window對象來訪問event對象,就像使用DOM0級方法時同樣。不過爲方便起見,同一個對象也會做爲參數傳遞。
若是是經過HTML特性指定的事件處理程序,那麼還能夠經過一個名叫event的變量來訪問event對象(與DOM中的事件模型相同)。
<input type='button' value='Click Me' onclick='alert(event.type)' >
複製代碼
IE的event對象一樣也包含與建立它的事件相關的屬性和方法。其中不少屬性和方法都有對應的或者相關的DOM屬性和方法。與DOM的event對象同樣,這些屬性和方法也會由於事件類型的不一樣而不一樣,但全部事件對象都會包含下表所列的屬性和方法。
屬性/方法 | 類型 | 讀/寫 | 說明 |
---|---|---|---|
cancelBubble | Boolean | 讀/寫 | 默認值false,但將其設置爲true就能夠取消事件冒泡(與DOM中的stopPropagation()方法的做用相同) |
returnValue | Boolean | 讀/寫 | 默認值爲true,將其設置爲false就能夠取消事件的默認行爲(與DOM中的preventDefault()方法的做用相同) |
srcElement | Element | 只讀 | 事件的目標(與DOM中的target屬性相同) |
type | String | 只讀 | 被觸發的事件的類型 |
由於事件處理程序的做用域是根據指定它的方式來肯定的,因此不能認爲this會始終等於事件目標。故而,最好仍是使用event.srcElement比較保險。
雖然DOM和IE中的event對象不一樣,但基於它們之間的類似性依舊能夠拿出跨瀏覽器的方案來。IE中event對象的所有信息和方法DOM對象中都有,只不過實現方式不同。不過,這種對應關係讓實現兩種事件模型之間的映射很是容易。能夠對前面介紹的EventUtil對象加以加強,添加以下方法以求同存異。
var EventUtil = {
addHandler: function (element, type, handler) {},
removeHandler: 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;
}
},
stopPropagation: function (event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
}
複製代碼
跨瀏覽器阻止事件冒泡:
var btn = document.getElementById('myBtn');
btn.onclick = function (event) {
alert('Clicked');
event = EventUtil.getEvent(event);
EventUtil.stopPropagation(event);
}
document.body.onclick = function (event) {
alert('Body clicked');
}
複製代碼
Web瀏覽器中可能發生的事件有不少類型。
「DOM3級事件」規定了如下幾類事件。
UI事件指的是那些不必定與用戶操做有關的事件。這些事件在DOM規範出現以前,都是以這個或那種形式存在的,而在DOM規範中保留是爲了向後兼容。
現有的UI事件以下:
<input>或<textarea>
)中的一或多個字符時觸發。多數這些事件都與window對象或表單控件相關。
要肯定瀏覽器是否支持DOM2級事件規定的HTML事件,可使用以下代碼:
var isSupported = document.implementation.hasFeature('HTMLEvents', '2.0');
複製代碼
最經常使用的一個事件。當頁面徹底加載後(包括全部圖像、JavaScript文件、CSS文件等外部資源),就會觸發window上面的load事件。
有兩種定義onload事件處理程序的方式。
第一種方式是使用以下所示的JavaScript代碼:
EventUtil.addHandler(window, 'load', function (event) {
alert('Loaded!');
})
複製代碼
第二種指定onload事件處理程序的方式是爲<body>
元素添加一個onload特性:
<!DOCTYPE html>
<html>
<head>
<title>Load Event Example</title>
</head>
<body onload='alert("loaded!")'>
</body>
</html>
複製代碼
通常來講,在window上面發生的任何事件均可以在<body/>
元素中經過相應的特性來指定,由於在HTML中沒法訪問window元素。實際上,這只是爲了保證向後兼容的一種權宜之計。建議儘量使用javascript方式。
圖像上面也能夠觸發load事件,不管是在DOM中的圖像元素仍是HTML中的圖像元素。所以,能夠在HTML中爲任何圖像指定onload事件處理程序:
<img src='smile.gif' onload='alert("Image loaded.")'>
複製代碼
這樣,當例子中的圖像加載完畢後就會顯示一個警告框。一樣的功能可使用JavaScript實現:
var $image = document.getElementById('myImg');
$image.addEventListener('load', function (e) {
alert('image loaded');
})
複製代碼
還有一些元素也以非標準的方式支持load事件。在IE9+、Firefox、Opera、Chrome和Safari 3+及更高版本中,<script>
元素也會觸發load事件,以便開發人員肯定動態加載的JavaScript文件是否加載完畢。與圖像不一樣,只有設置了<script>
元素的src屬性並將元素添加到文檔後,纔會開始下載JavaScript文件。換句話說,對於<script>
元素而言,指定src屬性和指定事件處理程序的前後順序就不重要了。
與load事件對應的是unload事件,這個事件在文檔被徹底卸載後觸發。只要用戶從一個頁面切換到另外一個頁面,就會發生unload事件。而利用這個事件最懂的狀況是清除引用,以免內存泄漏。
**不管使用那種方式,都要當心編寫onunload事件處理程序中的代碼。既然unload事件是在一切都被卸載以後才觸發,那麼在頁面加載後存在的那些對象,此時就不必定存在了。此時,操做DOM節點或者元素的樣式就會致使錯誤。
當瀏覽器窗口被調整到一個新的高度或寬度時,就會觸發resize事件。
關於什麼時候會觸發resize事件,不一樣瀏覽器有不一樣的機制。IE、Safari、Chrome和Opera會在瀏覽器窗口變化了1像素就觸發resize事件,而後隨着變化不斷重複觸發。Firefox則只會在用戶中止調整窗口大小時纔會觸發resize事件。
雖然scroll事件是在window對象上發生的,但它實際表示的則是頁面中相應元素的變化。
**在混雜模式下,能夠經過<body>
元素的scrollLeft和scrollTop來監控到這一變化;而在標準模式下,除Safari以外的全部瀏覽器都會經過<html>
元素來反映這一變化(Safari仍然基於<body>
跟蹤滾動位置)。 **
與resize事件相似,scroll事件也會在文檔被滾動期間重複被觸發,因此有必要儘可能保持事件處理程序的代碼簡單。
焦點事件會在頁面得到或失去焦點時觸發。利用這些事件並與document.hasFocus()方法及document.activeElement屬性配合,能夠知曉用戶在頁面上的行蹤,有如下6個焦點事件。
這一類事件中最主要的兩個是focus和blur,它們都是javscript早期就獲得全部瀏覽器支持的事件。這些事件的最大問題是不冒泡。所以,IE的focusin和focusout與Opera的DOMFocusIn和DOMFocusOut纔會重疊。IE的方式最後被DOM3級事件採納爲標準方式。
當焦點從頁面中的一個元素移動到另外一個元素,會依次觸發下列事件:
即便focus和blur不冒泡,也能夠在捕獲階段偵聽它們。
下面演示全局偵聽blur和focus
鼠標事件是Web開發中最經常使用的一類事件。DOM3級事件中定義了9個鼠標事件。
頁面中的全部元素都支持鼠標事件。除了mouseenter和mouseleave,全部鼠標事件都會冒泡,也能夠被取消,而取消鼠標事件將會影響瀏覽器的默認行爲。取消鼠標事件的默認行爲還會影響其餘事件,由於鼠標事件與其餘事件是密不可分的關係。
**只有在同一個元素上相繼觸發mousedown和mouseup事件,纔會觸發click事件;若是mousedown或mouseup中的一個被取消,就不會觸發click事件。相似地,只有觸發兩次click事件,纔會觸發一次dblclick事件。若是有代碼阻止了連續兩次觸發click事件,那麼就不會觸發dblclick事件了。
這4個事件觸發的順序始終以下:
使用如下代碼能夠檢測瀏覽器是否支持以上DOM2級事件(除dbclick、mouseenter和mouseleave以外):
var isSupported = document.implementation.hasFeature('MouseEvents', '2.0');
複製代碼
要檢測瀏覽器是否支持上面的全部事件,可使用如下代碼:
var isSupported = document.implementation.hasFeature('MouseEvents', '3.0');
複製代碼
鼠標事件中還有一類滾輪事件。而說是一類事件,其實就是一個mousewheel事件。這個事件跟蹤鼠標滾輪,相似於Mac的觸控板。
鼠標事件都是在瀏覽器視口中特定的位置上發生的。這個位置信息保存在事件對象的clientX和clientY屬性中。全部瀏覽器都支持這兩個屬性。
經過客戶區座標可以知道鼠標是在視口中什麼位置發生的,而頁面座標經過事件對象的pageX和pageY屬性,能告訴你事件是在頁面中的什麼位置發生的。換句話說,這兩個屬性表示鼠標光標在頁面中的位置,所以座標是從頁面自己而非視口的左邊和頂邊計算的。
鼠標事件發生時,不只會有相對於瀏覽器窗口的位置,還有一個相對於整個電腦屏幕的位置。而經過screenX和screenY屬性就能夠肯定鼠標事件發生時鼠標指針相對於整個屏幕的座標信息。
雖然鼠標事件主要是使用鼠標來觸發的,但在按下了鼠標時鍵盤上的某些鍵的狀態也能夠影響到所要採起的操做。這些修改鍵就是Shift、Ctrl、Alt和Meta,它們常常被用來修改鼠標事件的行爲。DOM爲此規定了4個屬性,表示這些修改鍵的狀態:shiftKey、ctrlKey、altKey和metaKey。
在發生mouseover和mouseout事件時,還會涉及更多的元素。這兩個事件都涉及把鼠標指針從一個元素的邊界以內移動到另外一個元素的邊界以內。對mouseover事件而言,事件的主目標是得到光標的元素,而相關元素就是那個失去光標的元素。相似的,對mouseout事件而言,事件的主目標是失去光標的元素,而相關元素則是得到光標的元素。
DOM經過event對象的relatdTarget屬性提供了相關元素的信息。這個屬性只對於mouseover和mouseout事件才包含值;對於其餘事件,這個屬性的值是null。IE8及以前版本不支持relatedTarget屬性,但提供了保存着一樣信息的不一樣屬性。在mouseover事件觸發時,IE的fromElement屬性中保存了相關元素;在mouseout事件觸發時,IE的toElement屬性中保存着相關元素。
只有在主鼠標按鈕被單擊時纔會觸發click事件,所以檢測按鈕的信息並非必要的。但對於mousedown和mouseup事件來講,則在其event對象存在一個button屬性,表示按下或釋放的按鈕。DOM的button屬性可能有以下3個值:0表示主鼠標按鈕,1表示中間的鼠標按鈕,2表示次鼠標按鈕。在常規的設置中,主鼠標按鈕就是鼠標左肩,而次鼠標按鈕就是鼠標右鍵。
在使用onmouseup事件處理程序時,button的值表示釋放的是哪一個按鈕。此外,若是不是按下或釋放了主鼠標按鈕,Opera不會觸發mouseup或mousedown事件。
「DOM2級事件」規範在event對象中還提供了detail屬性,用於給出有關事件的更多信息。對於鼠標事件來講,detail中包含了一個數值,表示在給定位置上發生了多少次單擊。在同一個像素上相繼地發生一次mousedown和一次mouseup事件算做一次單擊。detail屬性從1開始計數,每次單擊發生後都會遞增。若是鼠標在mousedown和mouseup之間移動了位置,則detail會被重置爲0。
IE6.0首先實現了mousewheel事件。此後,Opera、Chrome和Safari也都實現了這個事件。當用戶經過鼠標滾輪與頁面交互、在垂直方向上滾動頁面時,就會觸發mousewheel事件。這個事件能夠在任何元素上面觸發,最終會冒泡到document或window對象。與mousewheel事件對應的event對象除包含鼠標事件的全部標準信息外,還包含一個特殊的wheelDelta屬性。當用戶向前滾動鼠標滾輪時,wheelDelta是120的倍數;當用戶向後滾動鼠標滾輪時,wheelDelta是-120倍數。
IOS和android設備的實現很是特別,由於這些設備沒有鼠標。在面向iPhone和iPod中的Safari開發時,要記住如下幾點:
用戶在使用鍵盤時會觸發鍵盤事件。「DOM2級事件」最初規定了鍵盤事件,但在最終定稿以前又刪除了相應的內容。結果,對鍵盤事件的支持主要遵循的是DOM0級。
「DOM3級事件」爲鍵盤事件指定了規範,IE9率先徹底實現了該規範。其餘瀏覽器也在着手實現這一標準,但仍然有不少問題。
只有一個文本事件:textInput。這個事件是對keypress的補充,用意是在將文本顯示給用戶以前更容易攔截文本。在文本插入文本框以前會觸發textInput事件。
在用戶按了一下鍵盤上的字符鍵時,首先會觸發keydown事件,而後緊跟着是keypress事件,最後會觸發keyup事件。其中,keydown和keypress都是在文本框發生變化以前被觸發的;而keyup事件則是在文本框已經發生變化以後被觸發的。若是用戶按下了一個字符鍵不放,就會重複觸發keydown和keypress事件,直到用戶鬆開該鍵爲止。
若是用戶按下的是一個非字符鍵,那麼首先會觸發keydown事件,而後是keyup事件。若是按着不放,就會一直重複觸發keydown事件,直到用戶鬆開這個鍵,此時會觸發keyup事件。
鍵盤事件與鼠標事件同樣,都支持相同的修改鍵。並且,鍵盤事件的事件對象中也有shiftKey、ctrlKey、altKey和metaKey屬性。IE不支持metaKey。
在發生keydown和keyup事件時,event對象的keyCode屬性中會包含一個代碼,與鍵盤上一個特定的鍵對應。對數字字母字符鍵,keyCode屬性的值與ASCII碼中對應下泄字母或數字的編碼相同。
所以,數字鍵7的keyCode值爲55,而字母A鍵的keyCode值爲65 ———— 與Shift鍵的狀態無關。
發生keypress事件意味着按下的鍵會影響到屏幕中文本的顯示。在全部瀏覽器中,按下可以插入或刪除字符的鍵都會觸發keypress事件;按下其餘鍵可否觸發此事件因瀏覽器而異。
儘管全部瀏覽器都實現了某種形式的鍵盤事件,DOM3級事件仍是作出了一些改變。好比,DOM3級事件中的鍵盤事件,再也不包含charCode屬性,而是包含兩個新屬性:key和char。
其中,key屬性是爲了取代keyCode而新增的,它的值是一個字符串。在按下某個字符鍵時,key的值就是相應的文本字符(如k或M);在按下非字符鍵時,key的值是相應鍵的名(如Shift或Down)。而char屬性在按下字符鍵時的行爲與key相同,但在按下非字符鍵時值爲null。
DOM3級事件還添加了一個名爲location的屬性,這是一個數值,表示按下了什麼位置上的鍵:
「DOM3級事件」規範中引入了一個新事件,名叫textInput。根據規範,當用戶在可編輯區域中輸入字符時,就會觸發這個事件。這個用於替代keypress的textInput事件的行爲稍有不一樣。
因爲textInput事件主要考慮的是字符,所以它的event對象中包含一個data屬性,這個屬性的值就是用戶輸入的字符(而非字符編碼)。換句話說,用戶在沒有按上檔鍵的狀況下按下了S鍵,data的值就是‘s',而若是在按住上檔鍵時按下該鍵,data的值就是'S'。
任天堂Wii會在用戶按下Wii遙控器上的按鍵時觸發鍵盤事件。
複合事件是DOM3級事件中新添加的一類事件,用於處理IME的輸入序列。
IME(Input Method Editor,輸入法編輯器)可讓用戶輸入在物理鍵盤上找不到的字符。例如,使用拉丁文鍵盤的用戶經過IME照樣能輸入日文字符。IME一般須要同時按住多個鍵,但最終只輸入一個字符。複合事件就是針對檢測和處理這種輸入而設計的。
複合事件與文本事件有不少方面都很類似。在觸發複合事件時,目標是接收文本的輸入字段。但它比文本事件的事件對象多一個屬性data,其中包含如下幾個值中的一個:
利用監聽compositionstart判斷是否開啓了輸入法。從而實現體驗較爲良好兼容性較強的監控字數的控件。
DOM2級的變更(mutation)事件能在DOM中的某一部分發生變化時給出提示。變更事件是爲XML或HTML DOM設計的,並不特定於某種語言。DOM2級定義了以下變更事件。
使用下列代碼能夠檢測出瀏覽器是否支持變更事件:
var isSupported = document.implementation.hasFeature('MutationEvents', '2.0');
複製代碼
在使用removeChild()或replaceChild()從DOM中刪除節點時,首先會觸發DOMNodeRemoved事件。這個事件的目標(event.target)是被刪除的節點,而event.relatedNode屬性中包含着對目標節點父節點的引用。在這個事件觸發時,節點還沒有從其父節點刪除,所以其parentNode屬性仍然指向父節點。
若是被移除的節點包含子節點,那麼在其全部子節點以及這個被移除的節點上會相繼觸發DOMNodeRemovedFromDocument事件。但這個事件不會冒泡,因此只有直接指定給其中一個子節點的事件處理程序纔會被調用。這個事件的目標是相應的子節點或者那個被移除的節點,除此以外event對象中不包含其餘信息。
緊隨其後觸發的是DOMSubtreeModified事件。這個事件的目標是被移除節點的父節點;此時的event對象也不會提供與事件相關的其餘信息。
DOM規範沒有涵蓋全部瀏覽器支持的全部事件。HTML5詳盡列出了瀏覽器應該支持的全部事件。本節只討論其中獲得瀏覽器完善支持的事件,但並不是所有事件。
contextmenu這個事件,用以表示什麼時候應該顯示上下文菜單,以便開發人員取消默認的上下文菜單而提供自定義菜單。
因爲contextmenu事件是冒泡的,所以能夠爲document指定一個事件處理程序,用以處理頁面中發生的全部此類事件。這個事件的目標是發生用戶操做的元素。在全部瀏覽器中均可以取消這個事件。
這個事件的目標是發生用戶操做的元素。在全部瀏覽器中均可以取消這個事件:在兼容DOM的瀏覽器中,使用event.preventDefault();在IE中,將event.returnValue()的值,設置爲false。由於contextmenu事件屬於鼠標事件,因此其事件對象中包含與光標位置有關的全部屬性。一般使用contextmenu事件來顯示自定義的上下文菜單,而使用onclick事件處理程序來隱藏該菜單。
之因此發生在window對象上的beforeunload事件,是爲了讓開發人員有可能在頁面卸載前阻止這一操做。這個事件會在瀏覽器卸載頁面以前觸發,能夠經過它來取消卸載並繼續使用原有頁面。可是,不能完全取消這個事件,由於那就至關於讓用戶沒法離開當前頁面了。爲此,這個事件的意圖是將控制權交給用戶。
window的load事件會在頁面中的一切都加載完畢時觸發,但這個過程可能會由於要加載的外部資源過多而破費周折。而DOMContentLoaded事件則在造成完整的DOM樹以後就會觸發,不理會圖像、JavaScript文件、CSS文件或其餘資源是否已經下載完畢。
DOMContentLoaded事件對象不會提供任何額外的信息(其target屬性是document)。
**對於不支持DOMContentLoaded的瀏覽器,咱們建議在頁面加載期間設置一個時間爲0毫秒的超時調用:
setTimeout(function () {
// 在此添加事件處理程序
}, 0);
複製代碼
這個代碼的實際意思就是:"在當前JavaScript處理完成後當即運行這個函數。"在頁面下載和構建期間,只有一個JavaScript處理過程,所以超時調用會在該過程結束時當即觸發。爲了確保這個方法有效,必須將其做爲頁面中的第一個超時調用;即使如此,也仍是沒法保證在全部環境中該超時調用必定會早於load事件被觸發。
IE爲DOM文檔中的某些部分提供了readystatechange事件。這個事件的目的是提供與文檔或元素的加載狀態有關的信息。但這個事件的行爲有時候也很難預料。支持readystatechange事件的每一個對象都有一個readystate屬性,可能有下列5個值中的一個:
這些狀態看起來很直觀,但並不是全部對象都會經歷readyState的這幾個階段。換句話說,若是某個階段不適用某個對象,則該對象徹底可能跳過該階段;並無規定哪一個階段適用於哪一個對象。顯然,這意味着readystatechange事件常常會少於4次,而readyState屬性的值也不老是連續。
對於document而言,值爲「interactive」的readyState會在與DOMContentLoaded大體相同的時刻觸發readystatechange事件。此時,DOM樹已經加載完畢,能夠安全地操做它了,所以就會進入交互interactive階段。
這個事件的event對象不會提供任何信息,也沒有目標對象。
在與load事件一塊兒使用時,沒法預測兩個事件觸發的前後順序。在包含較多或較大的外部資源的頁面中,會在load事件觸發以前先進入交互階段;而在包含較少或較小的外部資源的頁面中,則很難說readystatechange事件會發生在load事件前面。
讓問題變得更復雜的是,交互階段可能會早於也可能會晚於完成階段出現,沒法確保順序。在包含較多外部資源的頁面中,交互階段更有可能早於完成階段出現;而在頁面中包含較少外部資源的狀況下,完成階段先於交互階段出現的可能性更大。所以,爲了儘量搶到先機,有必要同時檢測交互和完成階段。
var handler = function (e) {
if (document.readyState == 'interactive' || document.readyState == 'complete') {
document.removeEventListener('readystatechange', handler);
alert('Content loaded');
}
document.addEventListener('readystatechange', handler);
複製代碼
Firefox和Opera有一個特性,名叫「往返緩存」(back-forward cache),能夠在用戶使用瀏覽器的「後退」和「前進」按鈕時加快頁面的轉換速度。這個緩存中不只保存着頁面數據,還保存了DOM和JavaScript狀態;其實是將整個頁面都保存在了內存裏。若是頁面位於bfcache中,那麼再次打開該頁面時就不會觸發load事件。儘管因爲內存中保存了整個頁面的狀態,不觸發load事件也不該該會致使什麼問題,但爲了更形象地說明bfcache行爲,Firefox仍是提供了一些新事件。
第一個事件就是pageshow,這個事件在頁面顯示時觸發,不管該頁面是否來自bgcache。在從新加載的頁面中,pageshow會在load事件觸發後觸發;而對於bfcache中的頁面,pageshow會在頁面狀態徹底恢復的那一刻觸發。
另外值得注意的是,雖然這個事件的目標是document,但必須將其事件處理程序添加到window。
(function () {
var pageshowCount = 0;
window.addEventListener('pageshow', function (e) {
pageshowCount++;
notifyMe('page show: ' + pageshowCount);
})
})()
複製代碼
這個例子使用了私有做用域,以防止變量showCount進入全局做用域。當頁面首次加載完成時,showCount的值爲0。此後,每當觸發pageshow事件,showCount的值就會遞增並經過警告框顯示出來。若是你在離開包含以上代碼的頁面以後,又單擊「後退」按鈕返回該頁面,就會看到showCount每次遞增的值。這是由於該變量的狀態,乃至整個頁面的狀態,都被保存在了內存中,當你返回這個頁面時,它們的狀態獲得了恢復。若是你單擊了瀏覽器的「刷新」按鈕,那麼showCount的值就會被重置爲0,由於頁面已經徹底從新加載了。
除了一般的屬性以外,pageshow事件的event對象還包含了一個名爲presisted的布爾值屬性。若是頁面被保存在了bfcache中,則這個屬性的值爲true;不然,這個屬性的值爲false。
經過檢測persisted屬性,就能夠根據頁面在bfcache中的狀態來肯定是否須要採起其餘操做。
與pageshow事件對於的是pagehide事件,該事件會在瀏覽器卸載頁面的時候觸發,並且是在unload事件以前觸發。與pageshow事件同樣,pagehide在document上面觸發,但其事件處理程序必需要添加到window對象。這個事件的event對象也包含persisted屬性,不過其用途稍有不一樣。
HTML5新增了hashchange事件,以便在URL的參數列表發生變化時通知開發人員。之因此新增這個事件,是由於在Ajax應用中,開發人員常常要利用URL參數列來保存狀態或導航信息。
**使用如下代碼能夠檢測瀏覽器是否支持hashchange事件:
var isSupported = ('onhashchange' in window);
複製代碼
智能手機和平板電腦的普及,爲用戶與瀏覽器交互引入了一種新的方式,而一類新事件也應運而生。device event可讓開發人員肯定用戶在怎樣使用設備。
只要用戶改變了設備的查看模式,就會觸發orientationchange事件。此時的event對象不包含任何有價值的信息,由於惟一相關的信息能夠經過window。orientation訪問到。
全部IOS設備都支持orientationchange事件和window。orientation屬性。
firefox提供。這個事件與ios中的orientationchange事件不一樣,該事件只能提供一個平面的方向變化。因爲MozOrientation事件是在window對象上觸發的,因此可使用如下代碼來處理。
本質上,DeviceOrientation Event規範定義的deviceorientation事件與MozOrientation事件相似。它也是在加速計檢測到設備方向變化時在window對象上觸發,並且具備與MozOrientation事件相同的支持限制。
觸發deviceorientation事件時,事件對象中包含着每一軸相對於設備靜止狀態下發生變化的信息。事件對象包含如下5個屬性。
devicemotion事件。這個事件是要告訴開發人員設備何時移動,而不只僅是設備方向如何改變。
事件對象包含如下屬性:
具體來講有如下幾個觸摸事件:
上面這幾個事件都會冒泡,也均可以取消。雖然這些觸摸事件沒有在DOM規範中定義,但它們倒是以兼容DOM的方式實現的。所以,每一個觸摸事件的event對象都提供了在鼠標事件中常見的屬性。
除了常見的DOM屬性外,觸摸事件還包含下列三個用於跟蹤觸摸的屬性。
有三個手勢事件,分別介紹以下。
只有兩個手指都觸摸到事件的接收容器時纔會觸發這些事件。在一個元素上設置事件處理程序。若是另外一個手指又放在了屏幕上,則會先觸發gesturestart事件,隨後觸發基於該手指的touchstart事件。若是一個或兩個手指在屏幕上滑動,將會觸發gesturechange事件。但只要有一個手指移開,就會觸發gestureend事件,緊接着又會觸發基於該手指的touchend事件。
觸摸事件和手勢事件之間存在某種關係。當一個手指放在屏幕上時,會觸發touchstart事件。若是另外一個手指又放在了屏幕上,則會先觸發gesturestart事件,隨後觸發基於該手指的touchstart事件。若是一個或兩個手指在屏幕上滑動,將會觸發gesturechange事件。但只要有一個手指移開,就會觸發gestureend事件,緊接着又會觸發基於該手指的touchend事件。
與觸摸事件同樣,每一個手勢事件的event對象都包含着標準的鼠標事件屬性:bubbles、cancelable、view、clientX、clientY、screenX、screenY、detail、altKey、shiftKey、ctrlKey、metaKey。此外,還包含兩個額外的屬性:rotation和scale。
其中,rotation屬性表示手指變化引發的旋轉角度,負值表示逆時針旋轉,正值表示順時針旋轉。而scale屬性表示兩個手指間距離的變化狀況。
觸摸事件也會返回rotation和scale屬性,但這兩個屬性只會在兩個手指與屏幕保持接觸時纔會發生變化。通常來講,使用基於兩個手指的手勢事件,要比管理觸摸事件中的全部交互要容易得多。
在javascript中,添加到頁面上的事件處理程序數量將直接關係到頁面的總體運行性能。致使這一問題的緣由是多方面。首先,每一個函數都是對象,都會佔用內存;內存中的對象越多,性能就越差。其次,必須事先指定全部事件處理程序而致使的DOM訪問次數,會延遲整個頁面的交互就緒時間。事實上,從如何利用好事件處理程序的角度出發,仍是有一些方法可以提高性能的。
對「事件處理程序過多」問題的解決方案就是事件委託。
在document對象添加一個事件處理程序,用以處理頁面上發生的某種特定類型的事件。這樣作與採起傳統的作法相比具備以下優勢。
每當將事件處理程序指定給元素時,運行中的瀏覽器代碼與支持頁面交互的JavaScript代碼之間就會創建一個鏈接。這種鏈接越多,頁面執行起來就越慢。如前所述,能夠採用事件委託技術,限制創建的鏈接數量。另外,在不須要的時候移除事件處理程序,也是解決這個問題的一種方案。內存中留有那些過期不用的「空事件處理程序」(dangling event handler),也是形成web應用程序內存與性能問題的主要緣由。
在兩種狀況下。可能會形成上述問題。
事件常常由用戶操做或經過其餘瀏覽器功能來觸發。但不多有人直到,也可使用JavaScript在任意時刻來觸發特定的事件,而此時的事件就如同瀏覽器建立的事件同樣。
參考customEvent api
事件是將Javscript與網頁聯繫在一塊兒的主要方式。「DOM3級事件」規範和HTML5定義了常見的大多數事件。即便有規範定義了基本事件,但不少瀏覽器仍然在規範以外實現了本身的專有事件,從而爲開發人員提供更多掌握用戶交互的手段。
在使用事件時,須要考慮以下一些內存與性能方面的問題。
事件是JavaScript中最重要的主題之一,深刻理解事件的工做機制以及它們對性能的影響相當重要。