## Event 事件
客戶端JavaScript程序采用了異步事件驅動編程模型。
**一、相關術語**
`事件流`描述的是從頁面中接收事件的順序。
`事件`就是Web瀏覽器通知應用程序發生了什么事情。
`事件類型(event type)`是一個用來說明發生什么類型事件的字符串。例如,“mousemove”表示用戶移動鼠標,“keydown”表示鍵盤上某個鍵被按下等等。
`事件目標(event target)`是發生的事件與之相關的對象。當講事件時,我必須同時指明類型和目標。比如:window上的load事件或`<button>`元素的click事件。
在客戶端JavaScript應用程序中,Window、Document和Element對象是最常見的事件目標。
`事件處理程序(event handler)`或`事件監聽程序(event listener)`是處理或響應事件的函數。
`事件對象(event object)`是與特定事件相關且包含有關該事件詳細信息的對象。事件對象作為參數傳遞給事件處理程序函數(不包括IE8及之前版本,在這些瀏覽器中有時僅能通過全局變量event才能得到)。所有的事件對象都用來指定事件類型的type屬性和指定事件目標的target屬性。(在IE8及之前的版本中用srcElement而非target)
`事件傳播(event propagation)`是瀏覽器決定哪個對象觸發其事件處理程序的過程。當文檔元素上發生某個類型的事件時,它們會在文檔樹上向上傳播或“冒泡“(bubble)。
事件傳播的另一種方式:`事件捕獲(event capturing)`:在容器元素上注冊的特定處理程序有機會在事件傳播到真實目標之前捕獲它。
**二、Event事件**
**10.1注冊事件處理程序**
注冊事件處理程序有兩種基本方式:
**(1)一種是給事件目標對象或文檔元素設置屬性。**
按照約定,事件處理程序屬性的名字由“on”后面跟著事件名組成:onclick、onchange等。這些屬性名是區分大小寫的,所有都是`小寫`,即使是事件類型是由多個詞組成的(比如“readystatechange”)。
```
<div onclick="alert(1);"></div>
```
注:盡量少用內聯事件
還可以這樣:
```
var div = document.querySelector('div');
div.onclick=function(){
alert(1);
}
```
如果要刪除"on"類型的事件,只需將其設為null:
```
div.onclick = null;
```
再點擊就不會有任何反應。
事件處理程序屬性的缺點是其設計都是圍繞著假設每個事件目標對于每種事件類型將最多只有一個處理程序。
**(2)另一種是通過addEventListener()**
window對象、Document對象和所有的文檔元素(Element)都定義了一個名為`addEventListener()`方法,使用這個方法可以為事件目標注冊事件處理程序。
```
target.addEventListener(type, listener[, useCapture]);
```
addEventListener方法接受三個參數。
- type:事件名稱(事件類型),字符串,大小寫不敏感。
- listener:監聽函數。事件發生時,會調用該監聽函數。
- useCapture:布爾值,表示監聽函數是否在捕獲階段(capture)觸發,默認為false(監聽函數只在冒泡階段被觸發)。老式瀏覽器規定該參數必寫,較新版本的瀏覽器允許該參數可選。為了保持兼容,建議總是寫上該參數。
使用addEventListener()方法時,事件類型不應包括前綴“on”,比如:“onclick”改成“click”等。
```
addEventListener('click',listener,false);
```
注意:調用addEventListener()并不會影響onclick屬性的值。
```
<button id="mybutton">點擊</button>
var v = document.getElementById('mybutton');
v.onclick = function() {alert('1');}
v.addEventListener('click',function(){alert('2');},false);
```
上面的代碼中,單擊按鈕會產生兩個alert()對話框。
能通過多次調用addEventListener()方法為同一個對象注冊同一事件類型的多個處理程序函數。
所有該事件類型的注冊處理程序都會按照注冊的順序調用。使用相同的參數在同一個對象上多次調用addEventListener()是沒用的,處理程序仍然只注冊一次,同時重復調用也不會改變調用處理程序的順序(也就是說,如果為同一個事件多次添加同一個監聽函數,函數只會執行一次,多余的添加將自動刪除)。
相對`addEventListener()`的是 `removeEventListener() `方法。
removeEventListener方法的參數,與addEventListener方法完全一致。它的第一個參數“事件類型”,也是大小寫不敏感。
注意:removeEventListener()方法的事件處理程序函數必須是函數名。
**dispatchEvent()**
dispatchEvent方法在當前節點上觸發指定事件,從而觸發監聽函數的執行。該方法返回一個布爾值,只要有一個監聽函數調用了Event.preventDefault(),則返回值為false,否則為true。
```
target.dispatchEvent(event)
```
dispatchEvent方法的參數是一個Event對象的實例。
**在IE上**
IE支持事件冒泡流,不支持事件捕獲流。
IE9以前的IE不支持`addEventListener()`和`removeEventListener()`。不過我們可以使用類似的方法`attachEvent()`和`detachEvent()`;
`attachEvent()`和`detachEvent()`方法的工作原理與addEventListener()和removeEventListener()類似,但有所區別:
- 因為IE事件模型不支持事件捕獲,所以attachEvent()和detachEvent()只有兩個參數:事件類型和處理程序函數
- IE方法的第一個參數使用了帶“on”前綴的事件處理程序屬性名。例如:當給addEventListener()傳遞“click”時,要給attachEvent()傳遞“onclick”
- attachEvent()允許相同的事件處理程序函數注冊多次。當特定的事件類型發生時,注冊函數的調用次數和注冊次數一樣。
下面的代碼中,創建一個EventUtil工具類,可以兼容ie瀏覽器:
```
var EventUtil = {
addHandler: function(element, type, handler, useCapture) {
if(element.addEventListener) {
element.addEventListener(type, handler, useCapture ? true : false);
} else if(element.attachEvent) {
element.attachEvent('on' + type, handler);
} else if(element != window) {
element['on' + type] = handler;
}
},
removeHandler: function(element, type, handler, useCapture) {
if(element.removeEventListener) {
element.removeEventListener(type, handler, useCapture ? true : false);
} else if(element.detachEvent) {
element.detachEvent('on' + type, handler);
} else if(element != window) {
element['on' + type] = null;
}
}
};
```
在上面的EventUtil工具類中,我們創建addHandler()用來綁定事件,removeHandler用來刪除事件。
**10.2 事件處理程序的調用**
一旦注冊了事件處理程序,瀏覽器就會在指定對象上發生指定類型事件時自動調用它。
**10.2.1 事件處理程序的參數**
通常調用事件處理程序時把`事件對象(event)`作為它們的一個參數。事件對象的屬性提供了有關事件的詳細信息。例如,type屬性指定了發生的事件類型。
在IE8及以前的版本中,通過設置屬性注冊事件處理程序,當調用它們時并未傳遞事件對象。取而代之,需要通過全局對象window.event來獲得事件對象。
下面的代碼就是考慮了兼容性:
```
function handler(event){
event = event || window.event;
}
```
**10.2.2 event事件對象**
**(1)DOM中的事件對象**
event對象包含與創建它的特定事件有關的屬性和方法,觸發的事件類型不一樣,可用的屬性和方法也不一樣。不過,所有事件都有下列屬性和方法:
- bubbles 布爾值,只讀,表示事件是否冒泡
- cancelable 布爾值,只讀,表示是否可以取消事件的默認行為
- currentTarget Element類型,只讀,其事件處理程序當前正在處理事件的那個元素
- defaultPrevented 布爾值,只讀,為true時表示已經調用了preventDefault()
- detail Integer類型,只讀,與事件相關的細節信息
- eventPhase Integer類型,只讀,調用事件處理程序的階段:1表示捕獲階段,2表示目標階段,3表示冒泡階段
- preventDefault() Function類型,只讀,取消事件的默認行為
- stopImmediatePropagation() Function類型,只讀,取消事件的進一步捕獲或冒泡,同時組織任何事件處理程序被調用
- stopPropagation() Function類型,只讀,取消事件的進一步捕獲或冒泡
- target Element,只讀,事件的目標
- trusted 布爾值,只讀,為true時表示事件是瀏覽器生成的,為false時表示事件是開發人員創建的
- type String類型,只讀,事件類型
- view AbstractView,只讀,與事件關聯的抽象視圖,等同于發生事件的window對象
在事件處理程序內部,對象this始終等于currentTarget的值,而target則表示實際的目標。
**(2)IE中的事件對象**
對于IE,event對象是綁定在window對象中的:
```
window.event
```
IE的event對象也同樣包含與創建它的事件相關的屬性和方法,也具有一些共同屬性和方法:
- cancelBubble 布爾值,可讀寫,默認值為false,將其設置為true時,作用和DOM中的stoPropagation()方法一樣
- returnValue 布爾值,可讀寫,默認為true,將其設為false時,作用和DOM中的preventDefault()方法的作用一樣。
- srcElement Element,只讀,事件的目標(與DOM中的target屬性相同)
- type String,只讀,事件類型
基于IE和DOM事件對象不一樣,我們可以工具類:EventUtil里添加方法:
```
var EventUtil = {
addHandler: function(element, type, handler, useCapture) {
// 省略代碼
},
removeHandler: function(element, type, handler, useCapture) {
// 省略代碼
},
getEvent: function(event){
return event || window.event;
},
getTarget: function(evetn){
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;
}
}
};
```
我給工具類EventUtil添加了4個新方法,第一個是getEvent(),返回對event對象的引用;第二個是getTarget(),返回事件的目標;第三個是preventDefault(),用于取消事件的默認行為;第四個是stopPropagation(),用來取消事件冒泡。
調用方式也很簡單:
```
div.onclick = function(event){
event = EventUtil.getEvent(event);
target = event.getTarget();
EventUtil.preventDefault();
EventUtil.stopPropagation();
}
```
**10.2.2 事件處理程序的運行環境**
當通過設置屬性注冊事件處理程序時,看起來就好像是在文檔元素上定義了新方法:
```
e.onclick=function(){}
```
事件處理程序在事件目標上定義,所以它們作為這個對象的方法來調用。也就是說,在事件處理程序內,this關鍵字指向事件目標。
**10.2.3 事件處理程序的作用域**
事件處理程序在其定義的作用域而非調用時的作用域中執行,并且它們能存取那個作用域中的任何一個本地變量。
**10.2.4 事件處理程序的返回值**
通過設置對象屬性或HTML屬性注冊事件處理程序的返回值有時是非常有意義的。通常情況下,返回值false就是告訴瀏覽器不要執行這個事件相關的默認操作。比如,表單提交按鈕的onclick事件處理程序能返回false阻止瀏覽器提交表單。
```
v.onclick = function() {
return false;
}
```
理解事件處理程序的返回值只對通過屬性注冊的處理程序才有意義。
**10.2.5 調用順序**
文檔元素或其他對象可以指定事件類型注冊多個事件處理程序。當適當的事件發生時,瀏覽器必須按照下面的規則調用所有的事件處理程序:
- 通過設置對象屬性或HTML屬性注冊的處理程序一直優先調用。
- 使用addEventListener()注冊的處理程序按照它們的注冊順序調用。
- 使用attachEvent()注冊的處理程序可能按照任何順序調用,所以代碼不應該依賴于調用順序。
**10.2.6 事件傳播**
當事件目標是Window對象或其他一些單獨對象(比如XMLHttpRequest)時,瀏覽器會簡單的通過調用對象上適當的處理程序響應事件。
在調用在目標元素上注冊的事件處理函數后,大部分事件會“冒泡”到DOM樹根。
發生在文檔元素上的大部分事件都會冒泡,但有些例外,比如focus、blur和scroll事件。文檔元素上的load事件會冒泡,但它會在Document對象上停止冒泡而不會傳播到Window對象。只有當整個文檔都加載完畢時才會觸發window對象的load事件。
當事件目標是文檔或文檔元素時,它會在不同的DOM節點之間傳播(propagation)。
分為三個階段:
- 捕獲階段(capture phase):從window對象傳導到目標對象。(window--document--....--目標對象)
- 目標階段(target phase):目標對象本身的事件處理程序調用。
- 冒泡階段(bubbling phase):從目標對象傳導回window對象。(目標對象--父元素--....--document--window)
```
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div id="myDiv">點擊</div>
</body>
</html>
//事件捕獲階段,click事件的傳播順序
window
document
<html>
<body>
<div>
// 目標階段
div
// 事件冒泡階段,click事件的傳播順序
<div>
<body>
<html>
document
window
```
**事件代理(事件委托)**
基于事件會在冒泡階段向上傳播到父節點,我們可以將子節點的監聽事件定義在父節點上,由父節點的監聽函數統一處理多個子元素的事件。這種方法叫做事件的代理(delegation)。
```
<div id="div">
<div id="item">123</div>
</div>
document.getElementById('div').addEventListener('click', function(e) {
var target = e.target;
if(target.getAttribute('id').toLowerCase() == 'item') {
alert(1);
}
});
```
如果使用事件代理,以后插入的新節點仍然可以監聽的到。
如果使用JQuery,我們要為新增節點添加事件,除了在新增事件后添加事件外,還可以用下面的代碼:
```
$(document).on('click','div',function(){})
```
這種方式其實就是使用了事件代理。
**10.2.7 事件取消**
用屬性注冊的世界處理程序的返回值能用于取消事件的瀏覽器默認操作。在支持addEventListener()的瀏覽器中,也能通過調用事件對象的preventDefault()方法取消事件的默認操作。
在IE9之前的IE中,可以通過設置事件對象的returnValue屬性為false來達到同樣的效果。
```
function cancelHandler(event){
var event = event || window.event;
if(event.preventDefault) {event.preventDefault();} //標準
if(event.returnValue) { event.returnValue = false;} // IE
return false; //用于處理使用對象屬性注冊的處理程序
}
```
Event對象提供了一個屬性defaultPrevented,返回一個布爾值,默認false,表示該事件是否調用過preventDefault方法。
**取消事件傳播**
在支持addEventListener()的瀏覽器中,可以調用事件對象的一個stopPropagation()方法以阻止事件的繼續傳播。
```
e.stopPropagation()
//IE
e.cancelBubble = true;
```
在Event對象上還有一個方法`stopImmediatePropagation()`,阻止同一個事件的其他監聽函數被調用。也就是說,如果同一個節點對于同一個事件指定了多個監聽函數,這些函數會根據添加的順序依次調用。只要其中有一個監聽函數調用了stopImmediatePropagation方法,其他的監聽函數就不會再執行了。
```
e.addEventListener('click',function(event){
event.stopImmediatePropagation();
});
e.addEventListener('click',function(event){
//不會觸發
});
```
**10.3 文檔事件**
**(1)beforeunload事件、unload事件、load事件、error事件、pageshow事件、pagehide事件**
**beforeunload**
當瀏覽器將要跳轉到新頁面時觸發這個事件。如果事件處理程序返回一個字符串,那么它將出現在詢問用戶是否想離開當前頁面的標準對話框中。
```
window.addEventListener('beforeunload',function(e){
var message = '你確認要離開嗎!';
e.returnValue = message;
return message
});
```
**unload**
unload事件在窗口關閉或者document對象將要卸載時觸發,發生在window、body、frameset等對象上面。
它的觸發順序排在beforeunload、pagehide事件后面。unload事件只在頁面沒有被瀏覽器緩存時才會觸發,換言之,如果通過按下“前進/后退”導致頁面卸載,并不會觸發unload事件。
**load、error**
`load`事件直到文檔加載完畢時(包括所有圖像、JavaScript文件、CSS文件等外部資源)才會觸發。
`error`事件在頁面加載失敗時觸發。注意,頁面從瀏覽器緩存加載,并不會觸發load事件。
這兩個事件實際上屬于進度事件,不僅發生在document對象,還發生在各種外部資源上面。瀏覽網頁就是一個加載各種資源的過程,圖像(image)、樣式表(style sheet)、腳本(script)、視頻(video)、音頻(audio)、Ajax請求(XMLHttpRequest)等等。這些資源和document對象、window對象、XMLHttpRequestUpload對象,都會觸發load事件和error事件。
**pageshow、pagehide**
默認情況下,瀏覽器會在當前會話(session)緩存頁面,當用戶點擊“前進/后退”按鈕時,瀏覽器就會從緩存中加載頁面。
pageshow事件在頁面加載時觸發,包括第一次加載和從緩存加載兩種情況。如果要指定頁面每次加載(不管是不是從瀏覽器緩存)時都運行的代碼,可以放在這個事件的監聽函數。
pageshow事件有一個persisted屬性,返回一個布爾值。頁面第一次加載時,這個屬性是false;當頁面從緩存加載時,這個屬性是true。
pagehide事件與pageshow事件類似,當用戶通過“前進/后退”按鈕,離開當前頁面時觸發。
pagehide事件的event對象有一個persisted屬性,將這個屬性設為true,就表示頁面要保存在緩存中;設為false,表示網頁不保存在緩存中,這時如果設置了unload事件的監聽函數,該函數將在pagehide事件后立即運行。
**(2)DOMContentLoaded事件、readystatechange事件**
`DOMContentLoaded`事件:當文檔加載解析完畢且所有延遲(deferred)腳本(圖片未加載完畢)都執行完畢時會觸發,此時圖片和異步(async)腳本可能依舊在加載,但是文檔已經為操作準備就緒了。也就是說,這個事件,發生在load事件之前。
```
document.addEventListener('DOMContentLoaded',handler,false);
```
`readystatechange`事件:document.readyState屬性會隨著文檔加載過程而變,而每次狀態改變,Document對象上的readystatechange事件都會觸發。
```
document.onreadystatechange = function() {
if(document.readyState == 'complete'){
}
}
```
**(3)scroll事件、resize事件**
`scroll`事件在文檔(window)或文檔元素滾動時觸發,主要出現在用戶拖動滾動條。
`resize`事件在改變瀏覽器窗口大小時觸發,發生在window、body、frameset對象上面。
**(4)hashchange事件、popstate事件**
hashchange事件在URL的hash部分(即#號后面的部分,包括#號)發生變化時觸發。如果老式瀏覽器不支持該屬性,可以通過定期檢查location.hash屬性,模擬該事件。
popstate事件在瀏覽器的history對象的當前記錄發生顯式切換時觸發。注意,調用history.pushState()或history.replaceState(),并不會觸發popstate事件。該事件只在用戶在history記錄之間顯式切換時觸發,比如鼠標點擊“后退/前進”按鈕,或者在腳本中調用history.back()、history.forward()、history.go()時觸發。
**(5)cut事件、copy事件、paste事件**
這三個事件屬于文本操作觸發的事件。
- cut事件:在將選中的內容從文檔中移除,加入剪貼板后觸發。
- copy事件:在選中的內容加入剪貼板后觸發。
- paste事件:在剪貼板內容被粘貼到文檔后觸發。
這三個事件都有一個clipboardData只讀屬性。該屬性存放剪貼的數據,是一個DataTransfer對象。
**(6)焦點事件**
焦點事件發生在Element節點和document對象上。
- focus事件:Element節點獲得焦點后觸發,該事件不會冒泡。
- blur事件:Element節點失去焦點后觸發,該事件不會冒泡。
- focusin事件:Element節點將要獲得焦點時觸發,發生在focus事件之前。該事件會冒泡。Firefox不支持該事件。
- focusout事件:Element節點將要失去焦點時觸發,發生在blur事件之前。該事件會冒泡。Firefox不支持該事件。
這四個事件的事件對象,帶有target屬性(返回事件的目標節點)和relatedTarget屬性(返回一個Element節點)。對于focusin事件,relatedTarget屬性表示失去焦點的節點;對于focusout事件,表示將要接受焦點的節點;對于focus和blur事件,該屬性返回null。
由于focus和blur事件不會冒泡,只能在捕獲階段觸發,所以addEventListener方法的第三個參數需要設為true。
**10.4 鼠標事件**
**(1)click**
click事件當用戶在Element節點、document節點、window對象上,單擊鼠標(或者按下回車鍵)時觸發。
“鼠標單擊”定義為,用戶在同一個位置完成一次mousedown動作和mouseup動作。它們的觸發順序是:mousedown首先觸發,mouseup接著觸發,click最后觸發。
**(2)contextmenu**
contextmenu事件在一個節點上點擊鼠標右鍵時觸發,或者按下“上下文菜單”鍵時觸發。
可以通過下面的方式阻止“上下文菜單”的出現:
```
document.oncontextmenu=function(){
return false;
}
```
**(3)dblclick**
dblclick事件當用戶在element、document、window對象上,雙擊鼠標時觸發。該事件會在mousedown、mouseup、click之后觸發。
**(4)mousedown、mouseup**
mouseup事件在釋放按下的鼠標鍵時觸發。
mousedown事件在按下鼠標鍵時觸發。
**(5)mousemove**
mousemove事件當鼠標在一個節點內部移動時觸發。當鼠標持續移動時,該事件會連續觸發。為了避免性能問題,建議對該事件的監聽函數做一些限定,比如限定一段時間內只能運行一次代碼。
**(6)mouseover、mouseenter**
mouseover事件和mouseenter事件,都是鼠標進入一個節點時觸發。
兩者的區別是,mouseenter事件只觸發一次,而只要鼠標在節點內部移動,mouseover事件會在子節點上觸發多次。
**(7)mouseout、mouseleave**
mouseout事件和mouseleave事件,都是鼠標離開一個節點時觸發。
除了“mouseenter”和“mouseleave”外的所有鼠標事件都能冒泡。鏈接和提交按鈕上的click事件都有默認操作且能夠阻止。可以取消上下文菜單事件來阻止顯示上下文菜單。
傳遞給鼠標事件處理程序的事件對象有clientX和clientY屬性,它們指定了鼠標指針相對于包含窗口的坐標。加入窗口的滾動偏移量可以把鼠標位置轉換成文檔坐標。
**MouseEvent對象的屬性**
**(1)button、buttons**
`button`屬性指定當事件發生時哪個鼠標按鍵按下。
- -1:沒有按下鍵。
- 0:按下主鍵(通常是左鍵)。
- 1:按下輔助鍵(通常是中鍵或者滾輪鍵)。
- 2:按下次鍵(通常是右鍵)。
buttons屬性返回一個3個比特位的值,表示同時按下了哪些鍵。它用來處理同時按下多個鼠標鍵的情況。
- 1:二進制為001,表示按下左鍵。
- 2:二進制為010,表示按下右鍵。
- 4:二進制為100,表示按下中鍵或滾輪鍵。
同時按下多個鍵的時候,每個按下的鍵對應的比特位都會有值。比如,同時按下左鍵和右鍵,會返回3(二進制為011)。
注意:IE中的button屬性擁有不同的參數:
- 1:鼠標左鍵
- 4:鼠標中鍵
- 2:鼠標右鍵
**(2)clientX,clientY**
`clientX`屬性返回鼠標位置相對于瀏覽器窗口左上角的水平坐標,單位為像素,與頁面是否橫向滾動無關。
`clientY`屬性返回鼠標位置相對于瀏覽器窗口左上角的垂直坐標,單位為像素,與頁面是否縱向滾動無關。
**(3)movementX,movementY**
- movementX屬性返回一個水平位移,單位為像素,表示當前位置與上一個mousemove事件之間的水平距離。在數值上,等于currentEvent.movementX = currentEvent.screenX - previousEvent.screenX。
- movementY屬性返回一個垂直位移,單位為像素,表示當前位置與上一個mousemove事件之間的垂直距離。在數值上,等于currentEvent.movementY = currentEvent.screenY - previousEvent.screenY。
**(4)screenX,screenY**
`screenX`屬性返回鼠標位置相對于屏幕左上角的水平坐標,單位為像素。
`screenY`屬性返回鼠標位置相對于屏幕左上角的垂直坐標,單位為像素。
**(7)pageX、pageY**
`pageX`和`pageY`分別是觸點相對HTML文檔左邊沿的X坐標和觸點相對HTML文檔上邊沿的Y坐標。只讀屬性。
當存在滾動的偏移時,pageX包含了水平滾動的偏移,pageY包含了垂直滾動的偏移。
**(6)relatedTarget**
`relatedTarget`屬性返回事件的次要相關節點。對于那些沒有次要相關節點的事件,該屬性返回null。
**10.5 鼠標滾輪事件**
所有的現代瀏覽器都支持鼠標滾輪,并在用戶滾動滾輪時觸發事件。瀏覽器通常使用鼠標滾輪滾動或縮放文檔,但可以通過取消mousewheel事件來阻止這些默認操作。
所有瀏覽器都支持“mousewheel”事件,但Firefox使用“DOMMouseScroll”事件。
傳遞給“mousewheel”處理程序的事件對象有wheelDelta屬性,其指定用戶滾動滾輪有多遠(根據這個判斷滾動方向)。
遠離用戶方向的一次鼠標滾輪“單擊”的wheelDelta值通常是120,而接近用戶方向的一次“單擊”的值是-120。返回的總是120的倍數(120表明mouse向上滾動,-120表明鼠標向下滾動)
在Safari和Chrome中,為了支持使用二維軌跡球而非一維滾輪的Apple鼠標,除了wheelDelta屬性外,事件對象還有wheelDeltaX和wheelDeltaY,而wheelDelta和wheelDeltaY的值一直相同。
而在Firefox中,傳遞給“DOMMouseScroll”的屬性是detail。不過, detail屬性值的縮放比率和正負符號不同wheelDelta,detail值乘以-40和wheelDelta值相等。記錄其滾動距離的是“detail”屬性,它返回的是3的倍數(3表明mouse向下滾動,-3表明mouse向上滾動)。
```
window.onmousewheel = document.onmousewheel = scrollWheel;
function scrollWheel(e){
e = e || window.event;
if(e.wheelDelta) { //判斷瀏覽器IE,谷歌滑輪事件
if(e.wheelDelta > 0) {
//當滑輪向上滾動時
} else if(e.wheelDelta < 0) {
//當滑輪向下滾動時
};
} else if(e.detail) { //Firefox滑輪事件
if(e.detail < 0) {
//當滑輪向上滾動時
} else if(e.detail > 0) {
//當滑輪向下滾動時
};
};
}
```
**10.6 鍵盤事件**
鍵盤事件用來描述鍵盤行為,主要有keydown、keypress、keyup三個事件。
keydown:按下鍵盤時觸發該事件。
keypress:只要按下的鍵并非Ctrl、Alt、Shift和Meta,就接著觸發keypress事件。
keyup:松開鍵盤時觸發該事件。
textinput
任何時候,只要用戶輸入文本都會觸發。在Webkit瀏覽器中支持“textInput”事件。
事件對象屬性data(保存輸入文本),inputMethod屬性(用于指定輸入源)
注意:keypress和textinput事件是在新輸入的文本真正插入到聚焦的文檔元素前觸發的。
如果用戶一直按鍵不松開,就會重復觸發keydown、keypress,直到用戶松開才會觸發keyup。
**屬性**
**keycode**
指定了輸入字符的編碼。在Firefox中使用的是charCode屬性。
**altKey,ctrlKey,metaKey,shiftKey**
altKey、ctrlKey、metaKey和shiftKey屬性指定了當事件發生時是否有各種鍵盤輔助鍵按下。
altKey屬性:alt鍵
ctrlKey屬性:key鍵
metaKey屬性:Meta鍵(Mac鍵盤是一個四瓣的小花,Windows鍵盤是Windows鍵)
shiftKey屬性:Shift鍵
key,charCode key屬性返回一個字符串,表示按下的鍵名。如果同時按下一個控制鍵和一個符號鍵,則返回符號鍵的鍵名。比如,按下Ctrl+a,則返回a。如果無法識別鍵名,則返回字符串Unidentified。
主要功能鍵的鍵名(不同的瀏覽器可能有差異):Backspace,Tab,Enter,Shift,Control,Alt,CapsLock,CapsLock,Esc,Spacebar,PageUp,PageDown,End,Home,Left,Right,Up,Down,PrintScreen,Insert,Del,Win,F1~F12,NumLock,Scroll等。
charCode屬性返回一個數值,表示keypress事件按鍵的Unicode值,keydown和keyup事件不提供這個屬性。注意,該屬性已經從標準移除,雖然瀏覽器還支持,但應該盡量不使用。
**String.fromCharCode()**
一個keypress事件表示輸入的單個字符。事件對象以數字Unicode編碼的形式指定字符,所以必須用String.fromChatCode()把它轉換成字符串。
**10.7 表單事件**
**(1)input、propertychange**
檢測文本輸入元素的value屬性改變,這兩個事件是在新輸入的文本真正插入到聚焦的文檔元素前觸發的。
一般用`<inupt>`和`<textarea>`里,不過,當將contenteditable屬性設置為true時,只要值變化,也會觸發這兩個事件。
**(2)change**
當`<input>`、`<select>`和`<textarea>`的值發生變化時都會觸發change事件。只有全部修改完成時它才會觸發,這也是它和input事件的區別。
具體分下面幾種情況:
- 激活單選框(radio)或復選框(checkbox)時觸發。
- 用戶提交時觸發。比如,從下列列表(select)完成選擇,在日期或文件輸入框完成選擇。
- 當文本框或textarea元素的值發生改變,并且喪失焦點時觸發。
**(3)select**
當`<input>`和`<textarea>`中選中文本時觸發select事件。
**(4)reset、submit**
這兩個事件是發生在表單對象上,而不是發生在表單的成員上。
reset事件:當表單重置(所有表單成員的值變回默認值)時觸發。
submit事件:當表單數據向服務器提交時觸發。
注意:submit事件的發生對象是form元素,而不是button元素(即使它的類型是submit),因為提交的是表單,而不是按鈕。
**10.8 觸控事件**
觸控事件提供了響應用戶對觸摸屏或觸摸板上操作的能力。
觸控API提供了下面三個接口
- TouchEvent:代表當觸摸行為在平面上發生變化時發生的事件
- Touch:代表用戶與觸摸屏幕間的一個接觸點
- TouchList:代表一系列的Touch;一般在用戶多個手指同時解除屏幕時使用
**10.8.1 TouchEvent**
TouchEvent是一類描述手指在觸摸平面的狀態變化的事件。
**(1)觸摸事件的類型**
- touchstart:用戶接觸觸摸屏時觸發,它的target屬性返回發生觸摸的Element節點。
- touchend:用戶不再接觸觸摸屏時(或者移出屏幕邊緣時)觸發,它的target屬性與touchstart事件的target屬性是一致的,它的changedTouches屬性返回一個TouchList對象,包含所有不再觸摸的觸摸點(Touch對象)。
- touchmove:用戶移動觸摸點時觸發,它的target屬性與touchstart事件的target屬性一致。如果觸摸的半徑、角度、力度發生變化,也會觸發該事件。
- touchcancel:觸摸點取消時觸發,比如在觸摸區域跳出一個彈出框窗口(modal window)、觸摸點離開了文檔區域(進入瀏覽器菜單欄區域)、用戶放置更多的觸摸點(自動取消早先的觸摸點)。
- touchenter:當觸點進去某個elemen時觸發。沒有冒泡過程。
- touchleave:當觸點離開某個element時觸發。沒有冒泡過程。
**(2)TouchEvent的屬性**
**鍵盤屬性**
以下屬性都為只讀屬性,返回一個布爾值,表示觸摸的同時,是否按下某個鍵。
altKey 是否按下alt鍵
ctrlKey 是否按下ctrl鍵
metaKey 是否按下meta鍵
shiftKey 是否按下shift鍵
**changedTouches**
返回一個TouchList對象,包含了代表所有從上一次觸摸事件到此次事件過程中,狀態發生了改變的觸點的Touch對象。只讀屬性。
**targetTouches**
返回一個TouchList對象,包含了所有當前接觸觸摸平面的觸點的Touch對象(也可以說是處于活動狀態的觸點的Touch對象)。只讀屬性。
**touches**
返回一個TouchList對象,包含了所有當前接觸觸摸平面的觸點的Touch對象。只讀屬性。
**type**
此次觸摸事件的類型。
**target**
此次觸摸事件的目標元素(element)。這個目標元素對應 TouchEvent.changedTouches 中的觸點的起始元素。
**10.8.2 Touch**
Touch對象表示在觸控設備上的觸控點。通常是指手指或觸控筆在觸屏設備上的操作。
每個Touch對象代表一個觸點,每個觸點由其位置、大小、形狀、壓力大小和目標element描述。
**(1)Touch屬性**
以下屬性描述了用戶的觸摸行為
**identifier**
此Touch對象的唯一標識符。一次觸摸動作在平面上移動的整個過程中,該標識符不變,可以根據它來判斷跟蹤是否在同一次觸摸過程。只讀屬性。
**screenX、screenY**
screenX和screenY分別是觸點相對屏幕左邊沿的X坐標和觸點相對屏幕上邊沿的Y坐標。只讀屬性。
**clientX、clientY**
clientX和clientY分別是觸點相對于可視區左邊沿的X坐標和觸點相對可視區上邊沿的Y坐標。兩個屬性都不包括任何滾動偏移。只讀屬性。
**pageX、pageY**
pageX和pageY分別是觸點相對HTML文檔左邊沿的X坐標和觸點相對HTML文檔上邊沿的Y坐標。只讀屬性。
當存在滾動的偏移時,pageX包含了水平滾動的偏移,pageY包含了垂直滾動的偏移。
**radiusX、radiusY、rotationAngle**
radiusX:能夠包圍用戶和觸摸平面的接觸面的最小橢圓的水平軸(X軸)半徑. 這個值的單位和 screenX 相同。只讀屬性。
radiusY:能夠包圍用戶和觸摸平面的接觸面的最小橢圓的垂直軸(Y軸)半徑. 這個值的單位和 screenY 相同。只讀屬性。
rotationAngle:由radiusX 和 radiusY 描述的正方向的橢圓,需要通過順時針旋轉這個角度值,才能最精確地覆蓋住用戶和觸摸平面的接觸面,單位為度數,在0到90度之間。只讀屬性。
**force**
手指擠壓觸摸平面的壓力大小, 從0.0(沒有壓力)到1.0(最大壓力)的浮點數. 只讀屬性.
**target**
當這個觸點最開始被跟蹤時(在 touchstart 事件中), 觸點位于的HTML元素。也就是觸摸發生時的那個節點。
**10.8.3 TouchList**
一個TouchList代表一個觸摸平面上所有觸點的列表。比如一個用戶用三根手指接觸平面,與之相關的TouchList對于每根手指都會生成一個Touch對象,共計三個。
**(1)TouchList的屬性**
**length**
返回TouchList中Touch對象的數量,只讀屬性。
**(2)方法**
**identifiedTouch()**
返回列表中標識符與指定值匹配的第一個Touch對象。
**item()**
返回列表中以指定索引值的Touch對象。也可以使用數組的語法:touchlist[index]
**10.8.4 其他觸控事件**
gesturestart、gestureend
scale、rotation
**10.9 進度事件**
進度事件用來描述一個事件進展的過程。比如XMLHttpRequest對象發出的HTTP請求的過程、`<img>、<audio>、<video>、<style>、<link>`加載外部資源的過程。下載和上傳都會發生進度事件。
進度事件有以下幾種:
- abort事件:當進度事件被中止時觸發。如果發生錯誤,導致進程中止,不會觸發該事件。
- error事件:由于錯誤導致資源無法加載時觸發。
- load事件:進度成功結束時觸發。
- loadstart事件:進度開始時觸發。
- loadend事件:進度停止時觸發,發生順序排在error事件\abort事件\load事件后面。
- progress事件:當操作處于進度之中,由傳輸的數據塊不斷觸發。
- timeout事件:進度超過限時觸發。
**10.11 拖放事件**
拖放(Drag-and-Drop,DnD)是在“拖放源(drag source)”和“拖放目標(drop target)”之間傳輸數據的用戶界面。
拖拉的對象有好幾種,包括Element節點、圖片、鏈接、選中的文字等等。在HTML網頁中,除了Element節點默認不可以拖拉,其他(圖片、鏈接、選中的文字)都是可以直接拖拉的。為了讓Element節點可拖拉,可以將該節點的draggable屬性設為true。
```
<div draggable="true"> 此區域可拖拉 </div>
```
draggable屬性可用于任何Element節點,但是圖片(img元素)和鏈接(a元素)不加這個屬性,就可以拖拉。對于它們,用到這個屬性的時候,往往是將其設為false,防止拖拉。
注意:一旦某個Element節點的draggable屬性設為true,就無法再用鼠標選中該節點內部的文字或子節點了。
**10.11.1 拖放事件**
- dragstart:當一個元素開始被拖拽的時候觸發。用戶拖拽的元素需要附加dragstart事件。在這個事件中,監聽器將設置與這次拖拽相關的信息,例如拖動的數據和圖像。
- dragenter:當拖拽中的鼠標第一次進入一個元素的時候觸發。這個事件的監聽器需要指明是否允許在這個區域釋放鼠標。如果沒有設置監聽器,或者監聽器沒有進行操作,則默認不允許釋放。當你想要通過類似高亮或插入標記等方式來告知用戶此處可以釋放,你將需要監聽這個事件。
- dragover:當拖拽中的鼠標移動經過一個元素的時候觸發。大多數時候,監聽過程發生的操作與dragenter事件是一樣的。
- dragleave:當拖拽中的鼠標離開元素時觸發。監聽器需要將作為可釋放反饋的高亮或插入標記去除。
- drag:這個事件在拖拽源觸發。即在拖拽操作中觸發dragstart事件的元素。
- drop:這個事件在拖拽操作結束釋放時于釋放元素上觸發。一個監聽器用來響應接收被拖拽的數據并插入到釋放之地。這個事件只有在需要時才觸發。當用戶取消了拖拽操作時將不觸發,例如按下了Escape(ESC)按鍵,或鼠標在非可釋放目標上釋放了按鍵。
- dragend:拖拽源在拖拽操作結束將得到dragend事件對象,不管操作成功與否。
注意點:
拖拉過程只觸發以上這些拖拉事件,盡管鼠標在移動,但是鼠標事件不會觸發。
將文件從操作系統拖拉進瀏覽器,不會觸發dragStart和dragend事件。
dragenter和dragover事件的監聽函數,用來指定可以放下(drop)拖拉的數據。由于網頁的大部分區域不適合作為drop的目標節點,所以這兩個事件的默認設置為當前節點不允許drop。如果想要在目標節點上drop拖拉的數據,首先必須阻止這兩個事件的默認行為,或者取消這兩個事件。
```
<div ondragover="return false">
<div ondragover="event.preventDefault()">
下面是一個例子,將圖片拖放到另一個div中:
<div id="div1" ondrop="drop(event)" ondragover="allowDrop(event)"></div>
<img id="drag1" src="f.jpg" draggable="true" ondragstart="drag(event)" >
function allowDrop(ev)
{
ev.preventDefault();
}
function drag(ev)
{
ev.dataTransfer.setData("Text",ev.target.id);
}
function drop(ev)
{
ev.preventDefault();
var data=ev.dataTransfer.getData("Text");
ev.target.appendChild(document.getElementById(data));
}
```
**10.11.2 DataTransfer對象**
所有的拖拉事件都有一個dataTransfer屬性,用來保存需要傳遞的數據。返回一個DataTransfer對象。
拖拉的數據保存兩方面的數據:數據的種類(又稱格式)和數據的值。數據的種類是一個MIME字符串,比如 text/plain或者image/jpeg,數據的值是一個字符串。一般來說,如果拖拉一段文本,則數據默認就是那段文本;如果拖拉一個鏈接,則數據默認就是鏈接的URL。
當拖拉事件開始的時候,可以提供數據類型和數據值;在拖拉過程中,通過dragenter和dragover事件的監聽函數,檢查數據類型,以確定是否允許放下(drop)被拖拉的對象。比如,在只允許放下鏈接的區域,檢查拖拉的數據類型是否為text/uri-list。
發生drop事件時,監聽函數取出拖拉的數據,對其進行處理。
**(1)DataTransfer對象的屬性**
**dropEffect**
dropEffect屬性設置放下(drop)被拖拉節點時的效果,可能的值包括copy(復制被拖拉的節點)、move(移動被拖拉的節點)、link(創建指向被拖拉的節點的鏈接)、none(無法放下被拖拉的節點)。設置除此以外的值,都是無效的。
e.dataTransfer.dropEffect = 'copy';
dropEffect屬性一般在dragenter和dragover事件的監聽函數中設置,對于dragstart、drag、dragleave這三個事件,該屬性不起作用。
**effectAllowed**
effectAllowed屬性設置本次拖拉中允許的效果,可能的值包括copy(復制被拖拉的節點)、move(移動被拖拉的節點)、link(創建指向被拖拉節點的鏈接)、copyLink(允許copy或link)、copyMove(允許copy或move)、linkMove(允許link或move)、all(允許所有效果)、none(無法放下被拖拉的節點)、uninitialized(默認值,等同于all)。如果某種效果是不允許的,用戶就無法在目標節點中達成這種效果。
dragstart事件的監聽函數,可以設置被拖拉節點允許的效果;dragenter和dragover事件的監聽函數,可以設置目標節點允許的效果。
e.dataTransfer.effectAllowed = 'copy';
**files **
files屬性是一個FileList對象,包含一組本地文件,可以用來在拖拉操作中傳送。如果本次拖拉不涉及文件,則屬性為空的FileList對象。
**types**
types屬性是一個數組,保存每一次拖拉的數據格式,比如拖拉文件,則格式信息就為File。
**(2)DataTransfer對象的方法**
**setData()**
setData方法用來設置事件所帶有的指定類型的數據。它接受兩個參數,第一個是數據類型,第二個是具體數據。如果指定的類型在現有數據中不存在,則該類型將寫入types屬性;如果已經存在,在該類型的現有數據將被替換。
e.dataTransfer.setData('text/plain','bb');
**getData()**
getData方法接受一個字符串(表示數據類型)作為參數,返回事件所帶的指定類型的數據(通常是用setData方法添加的數據)。如果指定類型的數據不存在,則返回空字符串。通常只有drop事件觸發后,才能取出數據。如果取出另一個域名存放的數據,將會報錯。
**clearData()**
clearData方法接受一個字符串(表示數據類型)作為參數,刪除事件所帶的指定類型的數據。如果沒有指定類型,則刪除所有數據。如果指定類型不存在,則原數據不受影響。
```
e.dataTransfer.clearData('text/plain');
```
**setDragImage()**
拖動過程中(dragstart事件觸發后),瀏覽器會顯示一張圖片跟隨鼠標一起移動,表示被拖動的節點。這張圖片是自動創造的,通常顯示為被拖動節點的外觀,不需要自己動手設置。setDragImage方法可以用來自定義這張圖片,它接受三個參數,第一個是img圖片元素或者canvas元素,如果省略或為null則使用被拖動的節點的外觀,第二個和第三個參數為鼠標相對于該圖片左上角的橫坐標和右坐標。
**10.12 模擬事件**
模擬事件要經過三步:
- 創建event對象
- 初始化
- 使用dispatchEvent()方法觸發事件
**(1)document.createEvent()**
document.createEvent方法用來新建指定類型的事件。它所生成的Event實例,可以傳入dispatchEvent方法。
createEvent方法接受一個字符串作為參數,表示要創建的事件類型的字符串,可能值是:
- UIEvents: 一般化的UI事件(文檔事件)。鼠標事件和鍵盤事件都繼承自UI事件。DOM3級中是UIEvent
- MouseEvents:一般化的鼠標事件。DOM3中是MouseEvent
- MutationEvents:一般化的DOM變動事件。DOM3中是MutationEvent
- HTMLEvents:一般化的HTML事件。
**(2)event.initEvent()**
事件對象的initEvent方法,用來初始化事件對象,還能向事件對象添加屬性。該方法的參數必須是一個使用Document.createEvent()生成的Event實例,而且必須在dispatchEvent方法之前調用。
initEvent方法可以接受四個參數。
- type:事件名稱,格式為字符串。
- bubbles:事件是否應該冒泡,格式為布爾值。可以使用event.bubbles屬性讀取它的值。
- cancelable:事件是否能被取消,格式為布爾值。可以使用event.cancelable屬性讀取它的值。
- option:為事件對象指定額外的屬性。
```
var event = document.createEvent('MouseEvent');
event.initEvent('click',true,false);
div.dispatchEvent(event);
```
也可以使用相應的構造函數來創建event
```
var event = new MouseEvent('click', {
'bubbles': true,
'cancelable': true
});
div.dispatchEvent(event);
```
**10.13 自定義事件**
我們可以使用自定義事件
```
//新建事件實例
var event = new Event('play');
//添加監聽函數
element.addEventListener('play',funciton(e){},false);
//觸發事件
element.dispatchEvent(event);
```
**CustomEvent()**
Event構造函數只能指定事件名,不能在事件上綁定數據。如果需要在觸發事件的同時,傳入指定的數據,需要使用CustomEvent構造函數生成自定義的事件對象。
```
var event = new CustomEvent('play',{detail: 'play'});
//添加監聽函數
element.addEventListener('play',handler,false);
//觸發事件
element.dispatchEvent(event);
```
CustomEvent構造函數的第一個參數是事件名稱,第二個參數是一個對象。在上面的代碼中,該對象的detail屬性會綁定在事件對象之上。
```
funciton handler(e){
var data = e.detail;
}
```
在IE上,并不支持上面的自定義事件寫法,不過,我們可以采用老式寫法:
```
// 新建Event實例
var event = document.createEvent('Event');
// 事件的初始化
event.initEvent('play', true, true);
// 加上監聽函數
document.addEventListener('play', handler, false);
// 觸發事件
document.dispatchEvent(event);
```
- 前言
- JavaScript簡介
- 基本概念
- 語法
- 數據類型
- 運算符
- 表達式
- 語句
- 對象
- 數組
- 函數
- 引用類型(對象)
- Object對象
- Array對象
- Date對象
- RegExp對象
- 基本包裝類型(Boolean、Number、String)
- 單體內置對象(Global、Math)
- console對象
- DOM
- DOM-屬性和CSS
- BOM
- Event 事件
- 正則表達式
- JSON
- AJAX
- 表單和富文本編輯器
- 表單
- 富文本編輯器
- canvas
- 離線應用
- 客戶端存儲(Cookie、Storage、IndexedDB)
- HTML5 API
- Video/Audio
- Geolocation API
- requestAnimationFrame
- File API
- FullScreen API
- IndexedDB
- 檢測設備方向
- Blob
- vibrate
- Luminosity API
- WebRTC
- Page Visibility API
- Performance API
- Web Speech
- Notification
- 面向對象的程序設計
- 概述
- this關鍵字
- 原型鏈
- 作用域
- 常用API合集
- SVG
- 錯誤處理機制
- JavaScript開發技巧合集
- 編程風格
- 垃圾回收機制