事件就是用戶或瀏覽器自身執(zhí)行的某種動作。比如說 click,mouseover,都是事件的名字。而相應某個事件的函數就叫事件處理程序(或事件偵聽器)。為事件指定處理程序的方式有好幾種。
事件的作用范圍
//html
<div id="wrap">
<div id="outer">
<div id="inner"></div>
</div>
</div>
//css
#wrap {
width: 200px;
height: 200px;
background: pink;
}
#outer {
position: relative;
top: 50px;
left: 50px;
width: 100px;
height: 100px;
background: #eeddff;
}
#inner {
position: relative;
top: 25px;
left:25px;
width: 50px;
height: 50px;
background: #44ddff;
}
//js
var wrap = document.getElementById('wrap');
wrap.addEventListener('click',function(){
alert('789');
},false);
當點擊粉色塊和粉色外淺藍色部分的時候,都彈出了789,而淺藍色部分是嵌套在wrap元素之內的元素,故可得出結論,當元素注冊了事件,此事件的作用范圍為:1.元素自己所占頁面空間部分加嵌套元素所占空間范圍(若嵌套元素覆蓋在容器元素上,則事件的作用范圍為容器元素自身所占空間大小)
事件流
事件流描述的是從頁面中接受事件的順序。IE的事件流是事件冒泡流,而Netscape Communicator的事件流是事件捕獲流。
冒泡事件
IE的事件流叫做事件冒泡(event bubbing),即事件右最具體的元素接受,然后逐級向上傳播到不具體的元素,以下面的代碼為例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<input type="button" value='click me' id='myBtn'>
</body>
</html>
如果你單擊了#myBtn,那么在IE的頁面中,這個事件會如下傳播:
input->body->html->document
可以看到,事件首先在input上發(fā)生,input就是我們單擊的元素。然后事件沿著DOM樹向上傳播,一直到document對象。
所有的現代瀏覽器都支持事件冒泡。IE9,Firefox,Chrome和Safari則將事件一直冒泡到window對象。
事件捕獲
Netscape團隊提出的另一種事件流叫事件捕獲,事件捕獲的思想是不太具體的節(jié)點應該更早接收到事件,而最具體的節(jié)點應該最后接收到事件。以上面的代碼作為例子,那么單擊div的時候會按照與冒泡相反的順序觸發(fā)事件。
document->html->body->input
在這個過程中,document對象先接收到click事件,然后事件沿著DOM樹依次向下,一直傳遞到目標元素。
IE9,Firefox,Chrome和Safari都支持事件捕獲。“DOM2級事件”規(guī)范要求事件應該從document對象開始傳播,但實際上這些瀏覽器都是從window對象開始捕獲事件。
DOM事件流
規(guī)定的事件流包括三個階段:事件捕獲階段==>處于目標階段==>事件冒泡階段。首先發(fā)生的是事件捕獲階段,為截獲事件提供了機會。然后是實際的目標接收事件。最后一個階段是冒泡階段。
事件處理程序
HTML事件處理程序
<script type="text/javascript">
function show(){
alert('hello world!');
}
</script>
<input type="button" value="click me" onclick="show()"/>
在 html 中指定事件處理程序有兩個缺點。
- 存在一個時差問題。就本例子來說,假設 show()函數是在按鈕下方,頁面的最底部定義的,如果用戶在頁面解析 show()函數之前就單擊了按鈕,就會引發(fā)錯誤;
- html 與 javascript 代碼緊密耦合。如果要更換時間處理程序,就要改動兩個地方:html 代碼和 javascript 代碼。
Javascript 指定事件處理程序
1.DOM0事件處理程序
var btn=document.getElementById("myBtn"); //獲得對象元素的引用
btn.onclick=function(){
alert("Clicked!");
}
要使用JavaScript指定事件處理程序,必須先獲得對象元素的引用,然后為其指定事件處理程序的函數。
事件處理程序是在元素的作用域中運行的,也就是說程序中的this指向的是當前元素。
var btn=document.getElementById("myBtn"); //獲得對象元素的引用
btn.onclick=function(){
alert(this.id); //mybtn
}
通過將事件處理程序屬性的值設置成null就可以刪除事件處理程序。
btn.onclick = null;
2.DOM2級事件處理程序
“DOM2級事件”規(guī)定了兩個方法用于操作事件處理程序:addEventListener()和removeEventListener()。所有的節(jié)點都包含這兩個方法,接收三個參數:要處理的事件名,作為事件處理程序的函數和一個布爾值。最后的參數如果是true,表示在事件捕獲階段調用事件處理程序,如果是false,表示在事件冒泡階段調用事件處理程序。
var btn=document.getElementById("myBtn");
btn.addEventListener("click",function(){
alert(this.id);
},false);
DOM0級事件處理程序只能為一個元素添加唯一的某一個事件的處理程序。如果為一個元素添加了兩個click的處理程序,后定義的程序會覆蓋掉之前定義的程序,其實也就是給變量a多次賦值一樣。使用DOM2級事件處理程序的好處之一就是:可以添加多個添加多個事件處理程序。
var btn = document.getElementById('mybtn');
btn.addEventListener('click',function () {
alert('click me');
},false)
btn.addEventListener('click',function () {
alert(this.id)
},false);
這兩個事件處理程序會按照添加的順序觸發(fā)。
通過addEventListener()添加的事件處理程序只能使用removeEventListener()來移除。通過addEventListener()添加的匿名函數無法移除,因為移除是傳入的參數與添加處理程序時使用的參數必須相同。
function handle() {
alert('hello world')
}
btn.addEventListener('click',handle,false);
btn.removeEventListener('click',handle,false);
大多數情況下,都是就事件處理程序添加到事件流的冒泡階段,這樣可以最大限度的兼容各種瀏覽器。
IE中的事件處理程序
IE中有類似于DOM的兩個方法:attachEvent()和detachEvent()。這兩個方法接受兩個參數:事件處理程序名稱和事件處理程序函數。attachEvent()添加的事件處理程序都會添加到冒泡階段。
var btn=document.getElementById("myBtn");
btn.attachEvent("onclick",function(){
alert("click me");
});
需要注意的是,這里傳入的是‘onclick’而不是‘click’。
前面說到,在DOM0級事件中,事件處理程序的作用域是元素的作用域,而在使用attachEvent()時,作用域變成了全局作用域,此時this等于window
var btn=document.getElementById("myBtn");
btn.attachEvent("onclick",function(){
alert(this==widnow);//"true"
});
與addEventListener()一樣,attachEvent()也可以用來為一個元素添加多個事件處理程序,不過與DOM方法不同的是,事件處理程序不是按照添加的順序執(zhí)行,而是以相反的順序執(zhí)行。
可以使用detachEvent()移除使用attachEvent()添加的事件處理程序。與DOM方法一樣必須提供相同的參數,添加的匿名函數不能被移除。
因此,跨域瀏覽器的事件處理程序可以這樣寫:
var EventUtil = {
addHandler: function(element, type, handler){ // 該方法接受 3 個參數:要操作的元素,事件名稱和事件處理程序函數
if (element.addEventListener){ // 檢查傳入的元素是否存在 DOM2 級方法
element.addEventListener(type, handler, false); // 若存在,則使用該方法
} else if (element.addEvent){ // 如果存在的是 IE 的方法
element.attachEvent("on" + type, handler); // 則使用 IE 的方法,注意,這里的事件類型必須加上 "on" 前綴。
} else { // 最后一種可能是使用 DOM0 級
element["on" + type] = hander;
}
},
removeHandler: function(element, type, handler){ // 該方法是刪除之前添加的事件處理程序
if (element.removeEventListener){ // 檢查傳入的元素是否存在 DOM2 級方法
element.removeEventListener(type, handler, false); // 若存在,則使用該方法
} else if (element.detachEvent){ // 如果存在的是 IE 的方法
element.detachEvent("on" + type, handler); // 則使用 IE 的方法,注意,這里的事件類型必須加上 "on" 前綴。
} else { // 最后一種可能是使用 DOM0 及方法 (在現代瀏覽器中,應該不會執(zhí)行這里的代碼)
element["on" + type] = null;
}
}
};
var btn =document.getElementById("mybtn");
var hander= function(){
alert("clicked");
};
EventUtil.addHandler(btn,"click",hander);
EventUtil.removeHandler(btn,"click",hander); // 移除之前添加的事件處理程序
參考資料:
https://segmentfault.com/a/1190000003497939
http://wiki.jikexueyuan.com/project/brief-talk-js/event-handlers.html