- 相關(guān)推薦
深入分析原生JavaScript事件分析
JQuery這種Write Less Do More的框架,用多了難免會(huì)對(duì)原生js眼高手低。
小菜其實(shí)不想寫這篇博客,貌似很初級(jí)的樣子,但是看到網(wǎng)絡(luò)上連原生js事件綁定和解除都說(shuō)不明白,還是決定科普一下了。
首先聲明,小菜懂的也不是很多,只是把我的思路和大家分享一下。
DOM0事件模型
事件模型在不斷發(fā)展,早期的事件模型稱為DOM0級(jí)別。
DOM0事件模型,所有的瀏覽器都支持。
直接在dom對(duì)象上注冊(cè)事件名稱,就是DOM0寫法,比如:
復(fù)制代碼 代碼如下:
document.getElementById("test").onclick = function(e){};
意思就是注冊(cè)一個(gè)onclick事件。當(dāng)然,它和這種寫法是一個(gè)意思:
復(fù)制代碼 代碼如下:
document.getElementById("test")["onmousemove"] = function(e){};
這沒什么,只不過(guò)是兩種訪問(wèn)js對(duì)象屬性的方法,[]的形式主要是為了解決屬性名不是合法的標(biāo)識(shí)符,比如:object.123肯定報(bào)錯(cuò),但是object["123"]就避免了這個(gè)問(wèn)題,與此同時(shí),[]的寫法,也把js寫活了,用字符串表示屬性名稱,可以在運(yùn)行時(shí)動(dòng)態(tài)綁定事件。
言歸正傳,事件被觸發(fā)時(shí),會(huì)默認(rèn)傳入一個(gè)參數(shù)e,表示事件對(duì)象,通過(guò)e,我們可以獲取很多有用的信息,比如點(diǎn)擊的坐標(biāo)、具體觸發(fā)該事件的dom元素等等。
基于DOM0的事件,對(duì)于同一個(gè)dom節(jié)點(diǎn)而言,只能注冊(cè)一個(gè),后邊注冊(cè)的同種事件會(huì)覆蓋之前注冊(cè)的。例如:
復(fù)制代碼 代碼如下:
var btn = document.getElementById("test");
btn.onmousemove = function(e){
alert("ok");
};
btn["onmousemove"] = function(e){
alert("ok1");
};
結(jié)果會(huì)輸出ok1。
接下來(lái)再說(shuō)說(shuō)this。事件觸發(fā)時(shí),this就是指該事件在哪個(gè)dom對(duì)象上觸發(fā)。例如:
復(fù)制代碼 代碼如下:
var btn = document.getElementById("test");
btn.onmousemove = function(e){
alert(this.id);
};
結(jié)果輸出test。因?yàn)槭录褪窃趇d為test的dom節(jié)點(diǎn)上注冊(cè)的,事件觸發(fā)時(shí),this當(dāng)然代表這個(gè)dom節(jié)點(diǎn),可以理解為事件是被這個(gè)dom節(jié)點(diǎn)調(diào)用的。
所以,想解除事件就相當(dāng)簡(jiǎn)單了,只需要再注冊(cè)一次事件,把值設(shè)成null,例如:
復(fù)制代碼 代碼如下:
var btn = document.getElementById("test");
btn.onclick = function(e){
alert("ok");
};
btn.onclick = null;
原理就是最后注冊(cè)的事件要覆蓋之前的,最后一次注冊(cè)事件設(shè)置成null,也就解除了事件綁定。
事情還沒結(jié)束,DOM0事件模型還涉及到直接寫在html中的事件。例如:
復(fù)制代碼 代碼如下:
通過(guò)這種方式注冊(cè)的事件,同樣遵循覆蓋原則,同樣只能注冊(cè)一個(gè),最后一個(gè)生效。
區(qū)別就是,這樣注冊(cè)的事件,相當(dāng)于動(dòng)態(tài)調(diào)用函數(shù)(有點(diǎn)eval的意思),因此不會(huì)傳入event對(duì)象,同時(shí),this指向的是window,不再是觸發(fā)事件的dom對(duì)象。
DOM2事件模型
DOM2事件模型相對(duì)于DOM0,小菜僅僅了解如下兩點(diǎn):
· DOM2支持同一dom元素注冊(cè)多個(gè)同種事件。
· DOM2新增了捕獲和冒泡的概念。
DOM2事件通過(guò)addEventListener和removeEventListener管理,當(dāng)然,這是標(biāo)準(zhǔn)。
但I(xiàn)E8及其以下版本瀏覽器,自?shī)首詷罚愠隽藢?duì)應(yīng)的attachEvent和detachEvent,由于小菜才疏學(xué)淺,本文不做討論。
addEventListener當(dāng)然就是注冊(cè)事件,她有三個(gè)參數(shù),分別為:"事件名稱", "事件回調(diào)", "捕獲/冒泡"。舉個(gè)例子:
復(fù)制代碼 代碼如下:
var btn = document.getElementById("test");
btn.addEventListener("click", function(e){
alert("ok");
}, false);
事件名稱就不用多說(shuō)了,相比DOM0,去掉了前邊的on而已。
事件回調(diào)也很好理解,事件觸發(fā)了總得通知你吧!回調(diào)時(shí)和DOM0一樣,也會(huì)默認(rèn)傳入一個(gè)event參數(shù),同時(shí)this是指觸發(fā)該事件的dom節(jié)點(diǎn)。
最后一個(gè)參數(shù)是布爾型,true代表捕獲事件,false代表冒泡事件。其實(shí)很好理解,先來(lái)個(gè)示意圖:
意思就是說(shuō),某個(gè)元素觸發(fā)了某個(gè)事件,最先得到通知的是window,然后是document,依次而入,直到真正觸發(fā)事件的那個(gè)元素(目標(biāo)元素)為止,這個(gè)過(guò)程就是捕獲。接下來(lái),事件會(huì)從目標(biāo)元素開始起泡,再依次而出,直到window對(duì)象為止,這個(gè)過(guò)程就是冒泡。
為什么要這樣設(shè)計(jì)呢?這貌似是由于深厚的歷史淵源,小菜也不怎么了解,就不亂說(shuō)了。
由此可以看出,捕獲事件要比冒泡事件先觸發(fā)。
假設(shè)有這樣的html結(jié)構(gòu):
復(fù)制代碼 代碼如下:
然后我們?cè)谕鈱觗iv上注冊(cè)兩個(gè)click事件,分別是捕獲事件和冒泡事件,代碼如下:
復(fù)制代碼 代碼如下:
var btn = document.getElementById("test");
//捕獲事件
btn.addEventListener("click", function(e){
alert("ok1");
}, true);
//冒泡事件
btn.addEventListener("click", function(e){
alert("ok");
}, false);
最后,點(diǎn)擊內(nèi)層的div,先彈出ok1,后彈出ok。結(jié)合上邊的原理圖,外層div相當(dāng)于圖中的body,內(nèi)層div相當(dāng)于圖中最下邊的div,證明了捕獲事件先執(zhí)行,然后執(zhí)行冒泡事件。
為什么要強(qiáng)調(diào)點(diǎn)擊內(nèi)層的div呢?因?yàn)檎嬲|發(fā)事件的dom元素,必須是內(nèi)層的,外層dom元素才有機(jī)會(huì)模擬捕獲事件和冒泡事件,從原理圖上就看出了。
如果在真正觸發(fā)事件的dom元素上注冊(cè)捕獲事件和冒泡事件呢?
html結(jié)構(gòu)同上,js代碼如下:
復(fù)制代碼 代碼如下:
var btnInner = document.getElementById("testInner");
//冒泡事件
btnInner.addEventListener("click", function(e){
alert("ok");
}, false);
//捕獲事件
btnInner.addEventListener("click", function(e){
alert("ok1");
}, true);
當(dāng)然還是點(diǎn)擊內(nèi)層div,結(jié)果是先彈出ok,再?gòu)棾鰋k1。理論上應(yīng)該先觸發(fā)捕獲事件,也就是先彈出ok1,但是這里比較特殊,因?yàn)槲覀兪窃谡嬲|發(fā)事件的dom元素上注冊(cè)的事件,相當(dāng)于在圖中的div上注冊(cè),由圖可以看出真正觸發(fā)事件的dom元素,是捕獲事件的終點(diǎn),是冒泡事件的起點(diǎn),所以這里就不區(qū)分事件了,哪個(gè)先注冊(cè),就先執(zhí)行哪個(gè)。本例中,冒泡事件先注冊(cè),所以先執(zhí)行。
這個(gè)道理適用于多個(gè)同種事件,比如說(shuō)一下子注冊(cè)了3個(gè)冒泡事件,那么執(zhí)行順序就按照注冊(cè)的順序來(lái),先注冊(cè)先執(zhí)行。例如:
復(fù)制代碼 代碼如下:
var btnInner = document.getElementById("testInner");
btnInner.addEventListener("click", function(e){
alert("ok");
}, false);
btnInner.addEventListener("click", function(e){
alert("ok1");
}, false);
btnInner.addEventListener("click", function(e){
alert("ok2");
}, false);
結(jié)果當(dāng)然是依次彈出ok、ok1、ok2。
為了進(jìn)一步理解事件模型,還有一種場(chǎng)景,假如說(shuō)外層div和內(nèi)層div同時(shí)注冊(cè)了捕獲事件,那么點(diǎn)擊內(nèi)層div時(shí),外層div的事件一定是先觸發(fā)的,代碼如下:
復(fù)制代碼 代碼如下:
var btn = document.getElementById("test");
var btnInner = document.getElementById("testInner");
btnInner.addEventListener("click", function(e){
alert("ok");
}, true);
btn.addEventListener("click", function(e){
alert("ok1");
}, true);
結(jié)果是先彈出ok1。
假如外層div和內(nèi)層div都是注冊(cè)的冒泡事件,點(diǎn)擊內(nèi)層div時(shí),一定是內(nèi)層div事件先執(zhí)行,原理相同。
細(xì)心的讀者會(huì)發(fā)現(xiàn),對(duì)于div嵌套的情況,如果點(diǎn)擊內(nèi)層的div,外層的div也會(huì)觸發(fā)事件,這貌似會(huì)有問(wèn)題!
點(diǎn)擊的明明是內(nèi)層div,但是外層div的事件也觸發(fā)了,這的確是個(gè)問(wèn)題。
其實(shí),事件觸發(fā)時(shí),會(huì)默認(rèn)傳入一個(gè)event對(duì)象,前邊提過(guò)了,這個(gè)event對(duì)象上有一個(gè)方法:stopPropagation,通過(guò)此方法,可以阻止冒泡,這樣外層div就接收不到事件了。代碼如下:
復(fù)制代碼 代碼如下:
var btn = document.getElementById("test");
var btnInner = document.getElementById("testInner");
btn.addEventListener("click", function(e){
alert("ok1");
}, false);
btnInner.addEventListener("click", function(e){
//阻止冒泡
e.stopPropagation();
alert("ok");
}, false);
終于要說(shuō)說(shuō)怎么解除事件了。解除事件語(yǔ)法:btn.removeEventListener("事件名稱", "事件回調(diào)", "捕獲/冒泡");
這和綁定事件的參數(shù)一樣,詳細(xì)說(shuō)明下:
· 事件名稱,就是說(shuō)解除哪個(gè)事件唄。
· 事件回調(diào),是一個(gè)函數(shù),這個(gè)函數(shù)必須和注冊(cè)事件的函數(shù)是同一個(gè)。
· 事件類型,布爾值,這個(gè)必須和注冊(cè)事件時(shí)的類型一致。
也就是說(shuō),名稱、回調(diào)、類型,三者共同決定解除哪個(gè)事件,缺一不可。舉個(gè)例子:
復(fù)制代碼 代碼如下:
var btn = document.getElementById("test");
//將回調(diào)存儲(chǔ)在變量中
var fn = function(e){
alert("ok");
};
//綁定
btn.addEventListener("click", fn, false);
//解除
btn.removeEventListener("click", fn, false);
要想注冊(cè)過(guò)的事件能夠被解除,必須將回調(diào)函數(shù)保存起來(lái),否則無(wú)法解除。
DOM0與DOM2混用
事情本來(lái)就很亂了,這又來(lái)個(gè)混合使用,還讓不讓人活了。。。
別怕,混合使用完全沒問(wèn)題,DOM0模型和DOM2模型各自遵循自己的規(guī)則,互不影響。
整體上來(lái)說(shuō),依然是哪個(gè)先注冊(cè),哪個(gè)先執(zhí)行,其他就沒什么了。
后記
至此,原生js事件已經(jīng)講的差不多了,小菜僅僅知道這些而已,歡迎讀者補(bǔ)充其他知識(shí)點(diǎn)。
在實(shí)際應(yīng)用中,真正的行家不會(huì)傻傻的真的注冊(cè)這么多事件,一般情況下,只需在最外層dom元素注冊(cè)一次事件,然后通過(guò)捕獲、冒泡機(jī)制去找到真正觸發(fā)事件的dom元素,最后根據(jù)觸發(fā)事件的dom元素提供的信息去調(diào)用回調(diào)。
也就是說(shuō),行家會(huì)自己管理事件,而不依賴瀏覽器去管理,這樣即可以提高效率,又保證了兼容性,JQuery不就是這么做的嘛~
好了,教程到此結(jié)束,希望對(duì)讀者有所幫助!
【深入分析原生JavaScript事件分析】相關(guān)文章:
Javascript函數(shù)的定義和用法分析08-15
工程碩士gct考試邏輯考試深入分析01-11
javascript閉包的定義及應(yīng)用實(shí)例分析08-25
Javascript 閉包引起IE內(nèi)存泄露分析07-05
對(duì)javascript的理解08-08
常用的JavaScript模式09-22