[TOC]
* * * * *
> `JavaScript` 與 `HTML` 之間的交互是通過事件實現的。事件,就是用戶或者瀏覽器自身執行的某種操作。`DOM` 規定了一些事件,`BOM` 也支持一些事件。在 `IE9` 之前,`IE` 對事件都有自己的一套解釋,上面的都是遵循 `DOM` 的事件規范的
### 事件流
> 事件流描述的是從頁面中接收事件的順序,`IE` 的事件流是事件冒泡流,而 `Netscape` `Communicator` 的事件流是事件捕獲流。
> `DOM2` 級事件規定事件流包含三個階段:事件捕獲階段、處于目標階段和時間冒泡階段(實際中處于目標階段包含在此階段)

* * * * *
#### 事件冒泡
> 事件開始時由最具體的元素(文檔中嵌套層次最深的那個節點)接收,然后逐級向上傳播到較為不具體的節點(文檔)。
~~~
以下面的 html 頁面為例:
<html>
<head>
<title>click事件</title>
</head>
<body>
<div>click</div>
</body>
</html>
如果你單擊了頁面中的 `<div>` 元素,那么這個click事件會按照如下順序傳播:
` div -> body -> html ->document`
對于冒泡流的事件流機制,存在如下的兼容問題:
IE5.5 及更早版本: div -> body -> document
IE5.5 之后到IE9之前: div -> body -> html -> document
IE9、Firefox、Chrome 和 Safari:
div -> body -> html -> document -> window
~~~
~~~
<html>
<head>
<title>事件冒泡</title>
<meta charset="utf-8">
<body id='body'>
<div id="box">
<button id="my_btn">按鈕</button>
</div>
<script>
function bodyz(){
alert('我是body');
}
function boxz(){
alert('我是div');
}
function btnz(event){
alert('我是input');
// event.stopPropagation();
// 阻止事件冒泡之后點擊按鈕就不會出現 我是div、我是body
}
var body=document.getElementById('body'),
box=document.getElementById('box'),
btn=document.getElementById('my_btn');
box.addEventListener('click',boxz,false);
btn.addEventListener('click',btnz,false);
body.addEventListener('click',bodyz,false);
</script>
</body>
</head>
點擊按鈕會依次彈出:我是input、我是div、我是body
~~~
* * * * *
#### 事件捕獲
> 事件開始的時候由最不具體的節點接收,然后逐級向下傳播到最具體的節點。事件捕獲的用意在于在事件達到預定目標之前捕獲它。以上面的實例來看,`click` 事件的執行順序為:`document -> html -> body -> div`
> 雖然事件捕獲是 `Netscape Communicator `唯一支持的事件流模型,但 `IE9` 、`Firefox、Chrome、Opera` 和 `Safari` 目前也都支持這種事件流模型。盡管“ `DOM2 `級事件”規范要求事件應該從 `document` 對象開始傳播,但這些瀏覽器都是從 `window` 對象開始捕獲事件的。由于老版本的瀏覽器不支持,因此很少有人使用事件捕獲。建議大家放心地使用事件冒泡,在有特殊需要時在使用事件捕獲。
>
* * * * *
#### `DOM` 事件流
> `DOM2` 級事件規定的事件流包括三個階段:事件捕獲階段、處于目標階段和事件冒泡階段。首先發生的是事件捕獲,為截獲事件提供機會。然后實際的目標接收到事件。最后一個階段是冒泡階段,可以在這個階段對事件做出響應。
* * * * *
### 事件處理程序
#### `html` 事件處理程序
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script>
function showMessage(){
alert("HTML 事件處理程序");
}
</script>
<title>HTML 事件處理程序</title>
</head>
<body>
<button onclick="alert('HTML 事件處理程')">按鈕1</button>
<button onclick="showMessage()">按鈕2</button>
</body>
</html>
~~~


>[danger] 在 HTML 中指定事件處理程序有兩個缺點:
~~~
1. 存在一個時差問題。因為用戶可能會在 HTML 元素出現在頁面上就觸發相應的事件,但當時的事件處理程序有可能尚不具備執行條件。
2. HTML 與 JavaScript 代碼緊密耦合。如果要更換事件處理程序,就要改動兩個地方:HTML 代碼和 JavaScript 代碼。而這正是許多開發人員放棄 HTML 事件處理程序,轉而使用 JavaScript 指定事件處理程序的原因所在。
3. 如果處理函數是在綁定的位置之后解析的,那么就會報錯,所以最好使用 try{} catch() {} 語句塊
~~~
* * * * *
#### `DOM0` 級事件處理程序
> 通過JavaScript指定事件處理程序的傳統方式,就是將一個函數賦值給一個事件處理程序屬性。這種為事件處理程序賦值的方法是在第四代Web瀏覽器中出現的,而且至今仍然為所有現代瀏覽器所支持。原因一是簡單,二是具有跨瀏覽器的優勢。
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>DOM0級事件處理程序</title>
</head>
<body>
<button id="my_btn">按鈕</button>
<script>
var btn=document.getElementById('my_btn');
btn.onclick=function(){
// 這里可以使用 this 訪問到元素的任何屬性和方法
console.log('DOM0級事件處理程序');
setTimeout(function(){
btn.onclick=null; // 刪除事件處理程序
console.log('刪除事件處理程序');
},200);
}
</script>
</body>
</html>
~~~
以這種方式添加的時間處理程序會在時間流的冒泡階段被處理
* * * * *
#### `DOM2` 級事件處理程序
> “ `DOM2` 級事件”定義了兩個方法,用于處理指定和刪除事件處理程序的操作:`addEventListener()` 和 `removeEventListener()`。所有的 `DOM` 節點中都包含這兩個方法,并且它們都接受3個參數:要處理的事件名、作為事件處理程序的函數和一個布爾值。最后這個布爾值參數如果是 `true` ,表示在事件捕獲階段調用事件處理程序;如果是 `false` ,表示在冒泡階段調用事件處理程序。
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>DOM2級事件處理程序</title>
</head>
<body>
<button id="my_btn">按鈕</button>
<script>
var btn=document.getElementById('my_btn');
btn.addEventListener('click',function(){
// click不要on,三個參數。
alert('DOM2級事件處理程序1');
},false);
btn.addEventListener('click',function(){
alert('DOM2級事件處理程序2');
},false);
// 先輸出DOM2級事件處理程序1后 DOM2級事件處理程序2
</script>
</body>
</html>
~~~
>[danger] `DOM0` 和 `DOM2` 級事件處理程序都有一個共同的優點就是可以同時添加多個事件處理程序。使用 `removeEventListener()` 移除 `addEventListener()` 事件時,移除時傳入的參數與添加處理程序時使用的參數相同。如果添加的是匿名函數,那么將無法移除
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>DOM2級事件處理程序</title>
</head>
<body>
<button id="my_btn">按鈕</button>
<script>
var btn=document.getElementById('my_btn');
var handler=function(){
alert('DOM2級事件處理程序');
setTimeout(function(){
btn.removeEventListener('click',handler,false);
alert('刪除事件處理程序');
},1000);
};
btn.addEventListener('click',handler,false);
</script>
</body>
</html>
~~~
> 大多數情況下,都是將事件處理程序添加到事件流的冒泡階段,這樣可以最大限度地兼容各種瀏覽器。
* * * * *
#### IE事件處理程序(現代瀏覽器可忽略)
> 高程3:“ `IE8` 是最后一個仍然使用其專有事件系統的主要瀏覽器,`IE9` 開始就實現了 `DOM2` 級事件的核心部分”`IE` 實現了與 `DOM` 中類似的兩個方法:`attachEvent() `和 `detachEvent()` 。
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>IE事件處理程序</title>
</head>
<body>
<button id="my_btn">按鈕</button>
<script>
var btn=document.getElementById('my_btn');
btn.attachEvent('onclick',function(){
// click要加on,兩個參數。
alert('IE事件處理程序1');
});
btn.attachEvent('onclick',function(){
alert('IE事件處理程序2');
});
// 先輸出IE事件處理程序2后IE事件處理程序1.
</script>
</body>
</html>
使用detachEvent()移除attachEvent()事件時,移除時傳入的參數與添加處理程序時使用的參數相同。
~~~
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>IE事件處理程序</title>
</head>
<body>
<button id="my_btn">按鈕</button>
<script>
var btn=document.getElementById('my_btn');
var handler=function(){
alert('IE事件處理程序');
setTimeout(function(){
btn.detachEvent('onclick',handler);
alert('刪除事件處理程序');
},1000);
};
btn.attachEvent('onclick',handler);
</script>
</body>
</html>
~~~
* * * * *
#### 跨瀏覽器的事件處理程序(現代瀏覽器可忽略)
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>跨瀏覽器的事件處理程序</title>
</head>
<body>
<button id="my_btn">按鈕</button>
<script>
var EventUtil={
addHandler: function(element, type, handler){
if(element.addEventListener){ // DOM2級
element.addEventListener(type,handler,false);
}else if(element.attachEvent){ // IE
element.attachEvent('on'+type,handler);
}else{
element['on'+type]=handler; // DOM0級
}
},
removeHandler:function(element,type,handler){
if(element.removeEventListener){ // DOM2級
element.removeEventListener(type,handler,false);
}else if(element.detachEvent){ // IE
element.detachEvent('on'+type,handler);
}else{
element['on'+type]=null; // DOM0級
}
}
}
var btn=document.getElementById('my_btn');
var handler=function(){
alert('跨瀏覽器的事件處理程序');
setTimeout(function(){
EventUtil.removeHandler(btn,'click',handler);
alert('刪除事件處理程序');
},1000);
};
EventUtil.addHandler(btn,'click',handler);
// 事件類型不要加"on"
</script>
</body>
</html>
~~~
* * * * *
### 事件對象
~~~
參數設置方式
var btn=document.getElementById('my_btn');
btn.onclick=function(event) {}
btn.addEventListener('click', function(event) {})
~~~
~~~
實用屬性/方法:
1. .currentTarget 只讀 其事件處理程序當前正在處理事件的那個元素(事件程序冒泡或捕獲的過程中,上下文所處的元素);
2. .target 只讀 事件真正 被觸發的點;
3. .defaultPrevented readonly
true--已經調用了 preventDefault() false--沒有調用
4. .preventDefault() DOM3(新增) 如果 cancelable==true 則可執行該方法
5. .stopImmediatePropagation()
取消事件的進一步捕獲或冒泡,同時阻止任何事件處理程序被調用
6. .stopPropagation() 取消進一步捕獲或冒泡,需要 bubbles==true
7. .type 被觸發事件的類型(點擊后者焦點等)
~~~
>[warning] 只有事件處理程序執行期間,`event` 對象才會存在;一旦事件處理程序執行完,該對象就會被銷毀
### 模擬事件
~~~
事件,就是網頁中某個特別值得關注的瞬間。
DOM2中事件類型名是復數,DOM3中變成了單數。
1. UIEvents: 一般化的UI事件
2. MouseEvents: 一般化的鼠標事件
3. MutationEvents:一般化的DOM變動事件
~~~
~~~
1. 例子:模擬一個鼠標事件
var btn=document.getElementById('myBtn');
var event=document.createEvent('MouseEvents');
event.initMouseEvent('click', true, true, document.defaultView,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
btn.dispatchEvent(event);
這里接受15個參數,都是鼠標事件中存在的
~~~

~~~
2. 模擬一個自定義事件(DOM3),可能需要進行能力測試
document.implementation.hasFeature('CustomEvents', '3.0')
~~~

接受4個參數
