JavaScript和HTML的交互是通過事件實現(xiàn)的缔俄。JavaScript采用異步事件驅(qū)動編程模型弛秋,當文檔、瀏覽器俐载、元素或與之相關對象發(fā)生特定事情時蟹略,瀏覽器會產(chǎn)生事件。如果JavaScript關注特定類型事件瞎疼,那么它可以注冊當這類事件發(fā)生時要調(diào)用的句柄科乎。
理解事件流
事件流
比如有一個div
元素,div
里面有一個按鈕
贼急。點擊按鈕的同時茅茂,也觸發(fā)了div,甚至觸發(fā)了div所在的整個頁面太抓。
這個過程就是一個事件流空闲。
事件流描述的是從頁面中接收事件的順序,比如有兩個嵌套的div走敌,點擊了內(nèi)層的div碴倾,這時候是內(nèi)層的div先出發(fā)click事件還是外層先觸發(fā)?目前主要有三種模型
IE的事件冒泡:事件開始時由最具體的元素接收掉丽,然后逐級向上傳播到較為不具體的元素
Netscape的事件捕獲:不太具體的節(jié)點更早接收事件跌榔,而最具體的元素最后接收事件,和事件冒泡相反
DOM事件流:DOM2級事件規(guī)定事件流包括三個階段捶障,事件捕獲階段僧须,處于目標階段,事件冒泡階段项炼,首先發(fā)生的是事件捕獲担平,為截取事件提供機會,然后是實際目標接收事件锭部,最后是冒泡句階段暂论。
Opera、Firefox拌禾、Chrome取胎、Safari都支持DOM事件流,IE不支持事件流湃窍,只支持事件冒泡
如有以下html扼菠,點擊div區(qū)域
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style type="text/css">
</style>
</head>
<body>
<div>
Click Here
</div>
</body>
</html>
事件冒泡模型 | 事件捕獲模型 | DOM事件流 |
---|---|---|
事件處理程序(handler)
我們也稱之為事件偵聽器(listener)摄杂,事件就是用戶或瀏覽器自身執(zhí)行的某種動作坝咐。比如click循榆、load、moseover等墨坚,都是事件類型(俗稱事件名稱)秧饮,而響應某個事件的方法就叫做事件處理程序或者事件監(jiān)聽器或者事件句柄,事件處理程序名字是:on+事件類型泽篮。
HTML事件處理程序
所謂的HTML事件處理程序盗尸,其實就是說,你的這個事件是直接加在HTML
結構里的帽撑。
比如說我現(xiàn)在給input
按鈕寫一個鼠標點擊
事件泼各,點擊按鈕彈出hello
。
<input type="button" value="按鈕" onclick="alert('hello')">
我把onclick
事件亏拉,直接加到input
標簽上扣蜻,實在HTML
結構里,那么這樣的事件就叫做HTML事件
在HTML事件處理程序中可以包含要執(zhí)行的具體動作及塘,也可以調(diào)用在頁面其它地方定義的腳本,剛才的例子可以寫成這樣
<div id="box">
<input type="button" value="按鈕" id="btn" onclick="showMessage()">
</div>
<script type="text/javascript">
function showMessage(){
alert('hello world!')
}
</script>
我們把這種直接把事件加在HTML
結構里的元素上莽使,這種添加事件的方法叫做HTML事件
。
在HTML中指定事件處理程序書寫很方便笙僚,但是有兩個缺點损晤。
首先柑肴,存在加載順序問題,如果事件處理程序在html代碼之后加載,用戶可能在事件處理程序還未加載完成時就點擊按鈕之類的觸發(fā)事件赌朋,存在時間差問題。
其次叨咖,這樣書寫html代碼和JavaScript代碼緊密耦合蚯瞧,維護不方便。
DOM0級事件處理程序
比較傳統(tǒng)的方式掂铐。把一個函數(shù)賦值給一個事件的處理程序?qū)傩院狈鳌S玫谋容^多的方法,原因是簡單 跨瀏覽器的優(yōu)勢全陨。
通過JavaScript指定事件處理程序就是把一個方法賦值給一個元素的事件處理程序?qū)傩员唷C總€元素都有自己的事件處理程序?qū)傩裕@些屬性名稱通常為小寫辱姨,如onclick等柿菩,將這些屬性的值設置為一個方法,就可以指定事件處理程序雨涛,如下
<div id="box">
<input type="button" value="按鈕" id="btn" onclick="showMessage()">
<input type="button" value="按鈕2" id="btn2">
</div>
<script type="text/javascript">
function showMessage(){
alert('hello world!')
}
//DOM0級事件處理程序
//先把要執(zhí)行的按鈕取到
var btn2=document.getElementById("btn2"); //取得btn2按鈕對象
//給btn2添加onclick屬性枢舶,這個屬性觸發(fā)事件處理程序懦胞,這個事件處理程序可以寫成匿名函數(shù),也可以調(diào)用已經(jīng)存在的函數(shù)
btn2.onclick=function(){
alert(this.id)
}
</script>
這樣處理凉泄,事件處理程序被認為是元素的方法躏尉,事件處理程序在元素的作用域下運行,this就是當前元素后众,所以點擊button結果是:btn2
這樣還有一個好處胀糜,我們可以刪除事件處理程序,只需把元素的onclick屬性賦為null即可
或者
<div id="box">
<input type="button" value="按鈕" id="btn" onclick="showMessage()">
<input type="button" value="按鈕2" id="btn2">
</div>
<script type="text/javascript">
function showMessage(){
alert('hello world!')
}
//DOM0級事件處理程序
//先把要執(zhí)行的按鈕取到
var btn2=document.getElementById("btn2"); //取得btn2按鈕對象
//給btn2添加onclick屬性蒂誉,這個屬性觸發(fā)事件處理程序教藻,這個事件處理程序可以寫成匿名函數(shù),也可以調(diào)用已經(jīng)存在的函數(shù)
btn2.onclick=function(){
showMessage();
}
</script>
也可以刪掉這個事件
<div id="box">
<input type="button" value="按鈕" id="btn" onclick="showMessage()">
<input type="button" value="按鈕2" id="btn2">
</div>
<script type="text/javascript">
function showMessage(){
alert('hello world!')
}
//DOM0級事件處理程序
//先把要執(zhí)行的按鈕取到
var btn2=document.getElementById("btn2"); //取得btn2按鈕對象
//給btn2添加onclick屬性右锨,這個屬性觸發(fā)事件處理程序括堤,這個事件處理程序可以寫成匿名函數(shù),也可以調(diào)用已經(jīng)存在的函數(shù)
btn2.onclick=function(){
showMessage();
}
btn2.onclick=null;
DOM2事件處理程序
DOM2級事件定義了兩個方法用于處理指定和刪除事件處理程序的操作:addEventListener
和removeEventListener
绍移。所有的DOM節(jié)點都包含這兩個方法悄窃。
并且它們都接受三個參數(shù):
事件類型、
事件處理方法登夫、
一個布爾值广匙。最后布爾參數(shù)如果是true表示在捕獲階段調(diào)用事件處理程序,如果是false恼策,則是在事件冒泡階段處理鸦致。
剛才的例子我們可以這樣寫
<div id="box">
<input type="button" value="按鈕2" id="btn">
</div>
<script type="text/javascript">
var btn = document.getElementById("btn"); //取得btn按鈕對象
btn.addEventListener('click', function() {
alert(this.id);
}, false);
</script>
也可以這樣
<div id="box">
<input type="button" value="按鈕2" id="btn">
</div>
<script type="text/javascript">
function showelen() {
alert(this.id)
}
var btn = document.getElementById("btn"); //取得btn按鈕對象
btn.addEventListener('click', showelen, false);
</script>
上面代碼為button添加了click事件的處理程序,在冒泡階段觸發(fā)涣楷,與上一種方法一樣分唾,這個程序也是在元素的作用域下運行,不過有一個好處狮斗,我們可以為click事件添加多個處理程序
<div id="box">
<input type="button" value="按鈕2" id="btn">
</div>
<script type="text/javascript">
var btn = document.getElementById("btn"); //取得btn按鈕對象
btn.addEventListener('click', function() {
alert(this.id);
}, false);
btn.addEventListener('click', function() {
alert('Hello!');
}, false);
</script>
這樣兩個事件處理程序會在用戶點擊button后按照添加順序依次執(zhí)行绽乔。
通過addEventListener
添加的事件處理程序只能通過removeEventListener
移除,移除時參數(shù)與添加的時候相同碳褒,這就意味著剛才我們添加的匿名函數(shù)無法移除折砸,因為匿名函數(shù)雖然方法體一樣,但是句柄卻不相同沙峻,所以當我們有移除事件處理程序的時候可以這樣寫
<div id="box">
<input type="button" value="按鈕2" id="btn">
</div>
<script type="text/javascript">
function showelen() {
alert(this.id)
}
var btn = document.getElementById("btn"); //取得btn按鈕對象
btn.addEventListener('click', showelen, false);
btn.removeEventListener('click', showelen, false);
</script>
這樣寫的效果挺好睦授,但是要是低于IE8的瀏覽器,點擊是完全沒有效果的摔寨。下面我們來介紹ie兼容性問題去枷。
IE事件處理程序
IE并不支持addEventListener
和removeEventListener
方法,而是實現(xiàn)了兩個類似的方法attachEvent
和detachEvent
,這兩個方法都接收兩個相同的參數(shù)删顶,事件處理程序名稱和事件處理程序方法竖螃,由于IE指支持事件冒泡,所以添加的程序會被添加到冒泡階段逗余。
使用attachEvent添加事件處理程序可以如下
<div id="box">
<input type="button" value="按鈕" id="btn">
</div>
<script type="text/javascript">
var showelen=function() {
alert(this.id)
}
var btn = document.getElementById("btn"); //取得btn按鈕對象
btn.attachEvent('onclick',showelen);
</script>
結果是undefined特咆,很奇怪,一會兒我們會介紹到
使用attachEvent添加的事件處理程序可以通過detachEvent移除猎荠,條件也是相同的參數(shù)坚弱,匿名函數(shù)不能被移除。
<div id="box">
<input type="button" value="按鈕" id="btn">
</div>
<script type="text/javascript">
var showelen = function() {
alert('hello')
}
var btn = document.getElementById("btn"); //取得btn按鈕對象
btn.attachEvent('onclick', showelen);
btn.detachEvent('onclick', showelen);
</script>
那么現(xiàn)在回頭想想关摇,如果用DOM2事件處理程序
,IE就不能有效。如果用IE事件處理程序
,非IE瀏覽器就不能有效碾阁。那么這就是問題笆涫!
緊接著我們解決這個問題脂凶,跨瀏覽器的事件處理程序
宪睹。
跨瀏覽器的事件處理程序
如何進行跨瀏覽器的事件處理呢?
恰當?shù)氖褂媚芰z測蚕钦,什么叫能力檢測呢亭病?
就是說你有這個能力,你就用這個嘶居,你沒有這個能力你有別的能力那你就去用你別的能力罪帖。
也就是說你要是支持addEventListener
那你就用它。你要是支持attachEvent
那你就用它唄邮屁。
這個跨瀏覽器整袁,建議最好是封裝在一個對象內(nèi)。
var showelen = function() {
alert('hello')
}
var btn = document.getElementById("btn"); //取得btn按鈕對象
//跨瀏覽器事件處理程序
var eventutil={
//添加句柄
addhandler:function(element,type,handler){
if(element.addEventListener){ //判斷是否支持dom2級
element.addEventListener(type,handler,false); //支持就運行dom2級事件處理程序
}else if(element.attachEvent){ //判斷是否支持ie事件處理
element.attachEvent('on'+type,handler); //支持就運行ie事件處理程序 事件類型需要加'on'
}else{ //如果都不是就運行dom0級處理程序
element['on'+type]=handler;
//連接屬性的時候,所有用到'.'的地方都可以用[]中括號
//element.onclick===element['onclick']
}
},
//刪除句柄
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.addhandler(btn,'click',showelen);
//eventutil.removehandler(btn,'click',showelen)
連接屬性的時候,所有用到'.
'的地方都可以用[]中括號
element.onclick===element['onclick']
還可以這樣寫
<div id="box">
<input type="button" value="按鈕" id="btn">
</div>
<script type="text/javascript">
var showelen = function() {
alert('hello')
}
var btn = document.getElementById("btn"); //取得btn按鈕對象
//跨瀏覽器事件處理程序
function addEvent(node, type, handler) {
if (!node) return false;
if (node.addEventListener) {
node.addEventListener(type, handler, false);
return true;
} else if (node.attachEvent) {
node.attachEvent('on' + type, handler);
return true;
}
return false;
}
addEvent(btn,'click',showelen);
</script>
這樣佑吝,首先我們解決了第一個問題參數(shù)個數(shù)不同坐昙,現(xiàn)在三個參數(shù),采用事件冒泡階段觸發(fā)芋忿,第二個問題也得以解決炸客,如果是IE,我們給type添加上on戈钢,第四個問題目前還沒有解決方案痹仙,需要用戶自己注意,一般情況下逆趣,大家也不會添加很多事件處理程序蝶溶,試試這個方法感覺很不錯,但是我們沒有解決第三個問題,由于處理程序作用域不同抖所,如果handler內(nèi)有this之類操作梨州,那么就會出錯在IE下,實際上大多數(shù)函數(shù)都會有this操作田轧。
<div id="box">
<input type="button" value="按鈕" id="btn">
</div>
<script type="text/javascript">
var showelen = function() {
alert(this.id)
}
var btn = document.getElementById("btn"); //取得btn按鈕對象
//跨瀏覽器事件處理程序
function addEvent(node, type, handler) {
if (!node) return false;
if (node.addEventListener) {
node.addEventListener(type, handler, false);
return true;
} else if (node.attachEvent) {
node.attachEvent('on' + type, function() { handler.apply(node); });
//handler.apply(node),將原本屬于handler屬性方法交給對象node來使用了暴匠。
return true;
}
return false;
}
addEvent(btn,'click',showelen);
</script>
這樣處理就可以解決this的問題了,但是新的問題又來了傻粘,我們這樣等于添加了一個匿名的事件處理程序每窖,無法用detachEvent取消事件處理程序,有很多解決方案弦悉,我們可以借鑒大師的處理方式窒典,jQuery創(chuàng)始人John Resig是這樣做的
<div id="box">
<input type="button" value="按鈕" id="btn">
</div>
<script type="text/javascript">
var showelen = function() {
alert(this.id)
}
var btn = document.getElementById("btn"); //取得btn按鈕對象
//跨瀏覽器事件處理程序
function addEvent(node, type, handler) {
if (!node) return false;
if (node.addEventListener) {
node.addEventListener(type, handler, false);
return true;
} else if (node.attachEvent) {
node['e' + type + handler] = handler;
node[type + handler] = function() {
node['e' + type + handler](window.event);
};
node.attachEvent('on' + type, node[type + handler]);
return true;
}
return false;
}
addEvent(btn,'click',showelen);
</script>
在取消事件處理程序的時候
<div id="box">
<input type="button" value="按鈕" id="btn">
</div>
<script type="text/javascript">
var showelen = function() {
alert(this.id)
}
var btn = document.getElementById("btn"); //取得btn按鈕對象
//跨瀏覽器事件處理程序
function addEvent(node, type, handler) {
if (!node) return false;
if (node.addEventListener) {
node.addEventListener(type, handler, false);
return true;
} else if (node.attachEvent) {
node['e' + type + handler] = handler;
node[type + handler] = function() {
node['e' + type + handler](window.event);
};
node.attachEvent('on' + type, node[type + handler]);
return true;
}
return false;
}
function removeEvent(node, type, handler) {
if (!node) return false;
if (node.removeEventListener) {
node.removeEventListener(type, handler, false);
return true;
} else if (node.detachEvent) {
node.detachEvent('on' + type, node[type + handler]);
node[type + handler] = null;
}
return false;
}
addEvent(btn,'click',showelen);
removeEvent(btn,'click',showelen);
</script>
事件對象
什么是事件對象?在觸發(fā)DOM上的事件時都會產(chǎn)生一個對象稽莉。
事件對象event
DOM中的事件對象
<div id="box">
<input type="button" value="按鈕" id="btn">
</div>
<script type="text/javascript">
var btn = document.getElementById("btn"); //取得btn按鈕對象
btn.onclick = function(e) {
alert(e.type);//click
}
</script>
或者
<div id="box">
<input type="button" value="按鈕" id="btn">
</div>
<script type="text/javascript">
var showelen = function(e) {
alert(e.type); //click
}
var btn = document.getElementById("btn"); //取得btn按鈕對象
//跨瀏覽器事件處理程序
var eventutil={
//添加句柄
addhandler:function(element,type,handler){
if(element.addEventListener){ //判斷是否支持dom2級
element.addEventListener(type,handler,false); //支持就運行dom2級事件處理程序
}else if(element.attachEvent){ //判斷是否支持ie事件處理
element.attachEvent('on'+type,handler); //支持就運行ie事件處理程序 事件類型需要加'on'
}else{ //如果都不是就運行dom0級處理程序
element['on'+type]=handler;
//連接屬性的時候,所有用到'.'的地方都可以用[]中括號
//element.onclick===element['onclick']
}
},
//刪除句柄
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.addhandler(btn,'click',showelen);
//eventutil.removehandler(btn,'click',showelen)
</script>
點擊button的時候我們可以看到彈出內(nèi)容是click的彈窗
event對象包含與創(chuàng)建它的特定事件有關的屬性和方法瀑志,觸發(fā)事件的類型不同,可用的屬性和方法也不同污秆,但是所有事件都會包含
屬性/方法|類型|讀/寫|說明
----|------|----
bubbles|Boolean|只讀|事件是否冒泡
cancelable|Boolean|只讀|是否可以取消事件的默認行為
currentTarget|Element|只讀|事件處理程序當前處理元素
detail|Integer|只讀|與事件相關細節(jié)信息
eventPhase|Integer|只讀|事件處理程序階段:1 捕獲階段劈猪,2 處于目標階段,3 冒泡階段
preventDefault()|Function|只讀|取消事件默認行為
stopPropagation()|Function|只讀|取消事件進一步捕獲或冒泡
target|Element|只讀|事件的目標元素
type|String|只讀|被觸發(fā)的事件類型
view|AbstractView|只讀|與事件關聯(lián)的抽象視圖良拼,等同于發(fā)生事件的window對象
在事件處理程序內(nèi)部战得,this始終等同于currentTarget,而target是事件的實際目標庸推。
要阻止事件的默認行為常侦,可以使用preventDefault()方法,前提是cancelable值為true予弧,比如我們可以阻止鏈接導航這一默認行為
<a id="a" >連接</a>
<script type="text/javascript">
document.getElementById('a').onclick = function(e) {
e.preventDefault(); //取消事件默認行為
}
</script>
stopPaopagation()方法可以停止事件在DOM層次的傳播刮吧,即取消進一步的事件捕獲或冒泡。我們可以在button的事件處理程序中調(diào)用stopPropagation()從而避免注冊在body上的事件發(fā)生
看看區(qū)別
<div id="box">
<a id="a" >連接</a>
</div>
<script type="text/javascript">
document.getElementById('box').onclick = function(e) {
console.log('我是盒子box')
}
document.getElementById('a').onclick = function(e) {
console.log(e.type); //彈出事件類型
e.preventDefault(); //取消事件默認行為
}
</script>
點擊連接彈出事件類型后掖蛤,事件冒泡還會向外擴散認為你也點了box
如果不想這樣可以取消事件進一步捕獲或冒泡
<div id="box">
<a id="a" >連接</a>
</div>
<script type="text/javascript">
document.getElementById('box').onclick = function(e) {
console.log('我是盒子box')
}
document.getElementById('a').onclick = function(e) {
console.log(e.type); //彈出事件類型
e.preventDefault(); //取消事件默認行為
e.stopPropagation(); //取消事件進一步捕獲或冒泡
}
</script>
若是注釋掉e.stopPropagation(); 在點擊a的時候杀捻,由于事件冒泡,box的click事件也會觸發(fā)蚓庭,但是調(diào)用這句后致讥,事件會停止傳播。
IE中的事件對象
訪問IE中的event對象有幾種不同的方式器赞,取決于指定事件處理程序的方法垢袱。直接為DOM元素添加事件處理程序時,event對象作為window對象的一個屬性存在
<div id="box">
按鈕
</div>
<script type="text/javascript">
function handler() {
var e = window.event;
alert(e.type);
}
var box = document.getElementById('box');
box.onclick = handler;
</script>
我們通過window.event取得了event對象港柜,并檢測到了其類型请契,可是如果事件處理程序是通過attachEvent添加的咳榜,那么就會有一個event對象被傳入事件處理程序中
var handler = function(e) {
alert(e.type);
}
var box = document.getElementById('box');
attachEvent(box, handler);
當然這時候也可以通過window對象訪問event,方便起見爽锥,我們一般會傳入event對象涌韩,IE中所有的事件都包含以下屬性方法
屬性/方法|類型|讀/寫|說明
----|------|----
cancelBulle|Boolean|讀/寫|默認為false,設置為true后可以取消事件冒泡
returnValue|Boolean|讀/寫|默認為true氯夷,設為false可以取消事件默認行為
srcElement|Element|只讀|事件的目標元素
type|String|只讀|被觸發(fā)的事件類型
跨瀏覽器的事件對象
雖然DOM和IE的event對象不同臣樱,但基于它們的相似性,我們還是可以寫出跨瀏覽器的事件對象方案
<div id="box">
<a id="box_a" >按鈕</a>
</div>
<script type="text/javascript">
var box = document.getElementById('box');
var box_a = document.getElementById('box_a');
box.onclick=function(){
alert('我是整個父盒子');
}
box_a.onclick=function(e){
var e = getEvent(e); //為了兼容所有瀏覽器調(diào)用getEvent(e)返回event或者window.event
//e=e||window.event;
alert(getTarget(e)); //彈出事件的目標元素
preventDefault(e); //取消事件默認行為
stopPropagation(e) //可以取消事件冒泡
};
//返回event或者window.event
function getEvent(e) {
return e || window.event;
}
//事件的目標元素
function getTarget(e) {
return e.target || e.scrElement;
}
//取消事件默認行為
function preventDefault(e) {
if (e.preventDefault)
e.preventDefault();
else
e.returnValue = false;
}
//可以取消事件冒泡
function stopPropagation(e) {
if (e.stopPropagation)
e.stopPropagation();
else
e.cancelBubble = true;
}
</script>
針對兼容腮考,我們可以對這些功能進行封裝雇毫,以便后期更好的調(diào)用。
//跨瀏覽器事件處理程序
var eventutil={
//添加句柄
addhandler:function(element,type,handler){
if(element.addEventListener){ //判斷是否支持dom2級
element.addEventListener(type,handler,false); //支持就運行dom2級事件處理程序
}else if(element.attachEvent){ //判斷是否支持ie事件處理
element.attachEvent('on'+type,handler); //支持就運行ie事件處理程序 事件類型需要加'on'
}else{ //如果都不是就運行dom0級處理程序
element['on'+type]=handler;
//連接屬性的時候,所有用到'.'的地方都可以用[]中括號
//element.onclick===element['onclick']
}
},
//刪除句柄
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;
}
},
//獲取事件
getEvent: function(event) {
return event ? event : window.event;
},
//取消事件默認行為
preventDefault: function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
//阻止事件冒泡
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
},
//獲取事件屬性
gitType: function(event) {
return event.type;
},
//獲取事件的目標元素
getElement: function(event) {
return event.target || event.scrElement;
}
}
針對這篇文章踩蔚,做了個小demo棚放。歡迎Star。