與瀏覽器進(jìn)行交互的時(shí)候?yàn)g覽器就會(huì)觸發(fā)各種事件,。比如當(dāng)我們打開(kāi)某一個(gè)網(wǎng)頁(yè)的時(shí)候,瀏覽器加載完成了這個(gè)網(wǎng)頁(yè),,就會(huì)觸發(fā)一個(gè) load 事件,;當(dāng)我們點(diǎn)擊頁(yè)面中的某一個(gè)“地方”,瀏覽器就會(huì)在那個(gè)“地方”觸發(fā)一個(gè) click 事件,。
這樣,,我們就可以編寫 JavaScript,通過(guò)監(jiān)聽(tīng)某一個(gè)事件,,來(lái)實(shí)現(xiàn)某些功能擴(kuò)展,。例如監(jiān)聽(tīng) load 事件,顯示歡迎信息,,那么當(dāng)瀏覽器加載完一個(gè)網(wǎng)頁(yè)之后,,就會(huì)顯示歡迎信息,。
下面就來(lái)介紹一下事件。
基礎(chǔ)事件操作
監(jiān)聽(tīng)事件
瀏覽器會(huì)根據(jù)某些操作觸發(fā)對(duì)應(yīng)事件,,如果我們需要針對(duì)某種事件進(jìn)行處理,,則需要監(jiān)聽(tīng)這個(gè)事件。監(jiān)聽(tīng)事件的方法主要有以下幾種:
HTML 內(nèi)聯(lián)屬性(避免使用)
HTML 元素里面直接填寫事件有關(guān)屬性,,屬性值為 JavaScript 代碼,,即可在觸發(fā)該事件的時(shí)候,執(zhí)行屬性值的內(nèi)容,。
例如:
<button onclick="alert('你點(diǎn)擊了這個(gè)按鈕');">點(diǎn)擊這個(gè)按鈕</button>
onclick 屬性表示觸發(fā) click ,,屬性值的內(nèi)容(JavaScript 代碼)會(huì)在單擊該 HTML 節(jié)點(diǎn)時(shí)執(zhí)行。
顯而易見(jiàn),,使用這種方法,,JavaScript 代碼與 HTML 代碼耦合在了一起,不便于維護(hù)和開(kāi)發(fā),。所以除非在必須使用的情況(例如統(tǒng)計(jì)鏈接點(diǎn)擊數(shù)據(jù))下,,盡量避免使用這種方法。
DOM 屬性綁定
也可以直接設(shè)置 DOM 屬性來(lái)指定某個(gè)事件對(duì)應(yīng)的處理函數(shù),,這個(gè)方法比較簡(jiǎn)單:
element.onclick = function(event){
alert('你點(diǎn)擊了這個(gè)按鈕');
};
上面代碼就是監(jiān)聽(tīng) element 節(jié)點(diǎn)的 click 事件,。它比較簡(jiǎn)單易懂,而且有較好的兼容性,。但是也有缺陷,因?yàn)橹苯淤x值給對(duì)應(yīng)屬性,,如果你在后面代碼中再次為 element 綁定一個(gè)回調(diào)函數(shù),,會(huì)覆蓋掉之前回調(diào)函數(shù)的內(nèi)容。
雖然也可以用一些方法實(shí)現(xiàn)多個(gè)綁定,,但還是推薦下面的標(biāo)準(zhǔn)事件監(jiān)聽(tīng)函數(shù),。
使用事件監(jiān)聽(tīng)函數(shù)
標(biāo)準(zhǔn)的事件監(jiān)聽(tīng)函數(shù)如下:
element.addEventListener(<event-name>, <callback>, <use-capture>);
表示在 element 這個(gè)對(duì)象上面添加一個(gè)事件監(jiān)聽(tīng)器,當(dāng)監(jiān)聽(tīng)到有 <event-name> 事件發(fā)生的時(shí)候,,調(diào)用 <callback> 這個(gè)回調(diào)函數(shù),。至于 <use-capture> 這個(gè)參數(shù),表示該事件監(jiān)聽(tīng)是在“捕獲”階段中監(jiān)聽(tīng)(設(shè)置為 true)還是在“冒泡”階段中監(jiān)聽(tīng)(設(shè)置為 false),。關(guān)于捕獲和冒泡,,我們會(huì)在下面講解。
用標(biāo)準(zhǔn)事件監(jiān)聽(tīng)函數(shù)改寫上面的例子:
var btn = document.getElementsByTagName('button');
btn[0].addEventListener('click', function() {
alert('你點(diǎn)擊了這個(gè)按鈕');
}, false);
這里最好是為 HTML 結(jié)構(gòu)定義個(gè) id 或者 class 屬性,,方便選擇,,在這里只作為演示使用。
Demo:
移除事件監(jiān)聽(tīng)
當(dāng)我們?yōu)槟硞€(gè)元素綁定了一個(gè)事件,,每次觸發(fā)這個(gè)事件的時(shí)候,,都會(huì)執(zhí)行事件綁定的回調(diào)函數(shù),。如果我們想解除綁定,需要使用 removeEventListener 方法:
element.removeEventListener(<event-name>, <callback>, <use-capture>);
需要注意的是,,綁定事件時(shí)的回調(diào)函數(shù)不能是匿名函數(shù),,必須是一個(gè)聲明的函數(shù),因?yàn)榻獬录壎〞r(shí)需要傳遞這個(gè)回調(diào)函數(shù)的引用,,才可以斷開(kāi)綁定,。例如:
var fun = function() {
// function logic
};
element.addEventListener('click', fun, false);
element.removeEventListener('click', fun, false);
Demo:
事件觸發(fā)過(guò)程
在上面大體了解了事件是什么、如何監(jiān)聽(tīng)并執(zhí)行某些操作,,但我們對(duì)事件觸發(fā)整個(gè)過(guò)程還不夠了解,。
下圖就是事件的觸發(fā)過(guò)程,借用了 W3C 的圖片
捕獲階段(Capture Phase)
當(dāng)我們?cè)?DOM 樹(shù)的某個(gè)節(jié)點(diǎn)發(fā)生了一些操作(例如單擊,、鼠標(biāo)移動(dòng)上去),,就會(huì)有一個(gè)事件發(fā)射過(guò)去。這個(gè)事件從 Window 發(fā)出,,不斷經(jīng)過(guò)下級(jí)節(jié)點(diǎn)直到目標(biāo)節(jié)點(diǎn),。在到達(dá)目標(biāo)節(jié)點(diǎn)之前的過(guò)程,就是捕獲階段(Capture Phase),。
所有經(jīng)過(guò)的節(jié)點(diǎn),,都會(huì)觸發(fā)這個(gè)事件。捕獲階段的任務(wù)就是建立這個(gè)事件傳遞路線,,以便后面冒泡階段順著這條路線返回 Window,。
監(jiān)聽(tīng)某個(gè)在捕獲階段觸發(fā)的事件,需要在事件監(jiān)聽(tīng)函數(shù)傳遞第三個(gè)參數(shù) true ,。
element.addEventListener(<event-name>, <callback>, true);
但一般使用時(shí)我們往往傳遞 false,,會(huì)在后面說(shuō)明原因。
目標(biāo)階段(Target Phase)
當(dāng)事件跑啊跑,,跑到了事件觸發(fā)目標(biāo)節(jié)點(diǎn)那里,,最終在目標(biāo)節(jié)點(diǎn)上觸發(fā)這個(gè)事件,就是目標(biāo)階段,。
需要注意的時(shí),,事件觸發(fā)的目標(biāo)總是最底層的節(jié)點(diǎn)。比如你點(diǎn)擊一段文字,,你以為你的事件目標(biāo)節(jié)點(diǎn)在 div 上,,但實(shí)際上觸發(fā)在 <p> 、<span> 等子節(jié)點(diǎn)上,。例如:
在 Demo 中,,我監(jiān)聽(tīng)單擊事件,將目標(biāo)節(jié)點(diǎn)的 tag name 彈出,。當(dāng)你點(diǎn)擊加粗字體時(shí),,事件的目標(biāo)節(jié)點(diǎn)就為最底層的<strong> 節(jié)點(diǎn),。
冒泡階段(Bubbling Phase)
當(dāng)事件達(dá)到目標(biāo)節(jié)點(diǎn)之后,就會(huì)沿著原路返回,,由于這個(gè)過(guò)程類似水泡從底部浮到頂部,,所以稱作冒泡階段。
在實(shí)際使用中,,你并不需要把事件監(jiān)聽(tīng)函數(shù)準(zhǔn)確綁定到最底層的節(jié)點(diǎn)也可以正常工作,。比如在上例,你想為這個(gè)<div> 綁定單擊時(shí)的回調(diào)函數(shù),,你無(wú)須為這個(gè) <div> 下面的所有子節(jié)點(diǎn)全部綁定單擊事件,,只需要為 <div> 這一個(gè)節(jié)點(diǎn)綁定即可。因?yàn)榘l(fā)生它子節(jié)點(diǎn)的單擊事件,,都會(huì)冒泡上去,,發(fā)生在 <div> 上面。
針對(duì)這三個(gè)階段,,wilsonpage 做了一個(gè)非常棒的 Demo,,可以看下:
為什么不用第三個(gè)參數(shù) true
介紹完上面三個(gè)事件觸發(fā)階段,我們來(lái)看下這個(gè)問(wèn)題,。
所有介紹事件的文章都會(huì)說(shuō),,在使用 addEventListener 函數(shù)來(lái)監(jiān)聽(tīng)事件時(shí),第三個(gè)參數(shù)設(shè)置為 false ,,這樣監(jiān)聽(tīng)事件時(shí)只會(huì)監(jiān)聽(tīng)冒泡階段發(fā)生的事件,。
這是因?yàn)?IE 瀏覽器不支持在捕獲階段監(jiān)聽(tīng)事件,為了統(tǒng)一而設(shè)置的,,畢竟 IE 瀏覽器的份額是不可忽略的,。
IE 瀏覽器在事件這方面與標(biāo)準(zhǔn)還有一些其他的差異,我們會(huì)在后面集中介紹,。
使用事件代理(Event Delegate)提升性能
因?yàn)槭录忻芭輽C(jī)制,所有子節(jié)點(diǎn)的事件都會(huì)順著父級(jí)節(jié)點(diǎn)跑回去,,所以我們可以通過(guò)監(jiān)聽(tīng)父級(jí)節(jié)點(diǎn)來(lái)實(shí)現(xiàn)監(jiān)聽(tīng)子節(jié)點(diǎn)的功能,,這就是事件代理。
使用事件代理主要有兩個(gè)優(yōu)勢(shì):
- 減少事件綁定,,提升性能,。之前你需要綁定一堆子節(jié)點(diǎn),而現(xiàn)在你只需要綁定一個(gè)父節(jié)點(diǎn)即可,。減少了綁定事件監(jiān)聽(tīng)函數(shù)的數(shù)量,。
- 動(dòng)態(tài)變化的 DOM 結(jié)構(gòu),仍然可以監(jiān)聽(tīng),。當(dāng)一個(gè) DOM 動(dòng)態(tài)創(chuàng)建之后,,不會(huì)帶有任何事件監(jiān)聽(tīng),,除非你重新執(zhí)行事件監(jiān)聽(tīng)函數(shù),而使用事件監(jiān)聽(tīng)無(wú)須擔(dān)憂這個(gè)問(wèn)題,。
看一個(gè)例子:
上面例子中,,為了簡(jiǎn)便,我使用 jQuery 來(lái)實(shí)現(xiàn)普通事件綁定和事件代理,。我的目標(biāo)是監(jiān)聽(tīng)所有 a 鏈接的單擊事件,,.ul1 是常規(guī)的事件綁定方法,jQuery 會(huì)循環(huán)每一個(gè) .ul > a 結(jié)構(gòu)并綁定事件監(jiān)聽(tīng)函數(shù),。.ul2 則是事件監(jiān)聽(tīng)的方法,,jQuery 只為 .ul2 結(jié)構(gòu)綁定事件監(jiān)聽(tīng)函數(shù),因?yàn)?nbsp;.ul2 下面可能會(huì)有很多無(wú)關(guān)節(jié)點(diǎn)也會(huì)觸發(fā)click 事件,,所以我在 on 函數(shù)里傳遞了第二個(gè)參數(shù),,表示只監(jiān)聽(tīng) a 子節(jié)點(diǎn)的事件。
它們都可以正常工作,,但是當(dāng)我動(dòng)態(tài)創(chuàng)建新 DOM 結(jié)構(gòu)的時(shí)候,,第一個(gè) ul 問(wèn)題就出現(xiàn)了,新創(chuàng)建結(jié)構(gòu)雖然還是.ul1 > a ,,但是沒(méi)有綁定事件,,所以無(wú)法執(zhí)行回調(diào)函數(shù)。而第二個(gè) ul 工作的很好,,因?yàn)辄c(diǎn)擊新創(chuàng)建的 DOM ,,它的事件會(huì)冒泡到父級(jí)節(jié)點(diǎn)進(jìn)行處理。
如果使用原生的方式實(shí)現(xiàn)事件代理,,需要注意過(guò)濾非目標(biāo)節(jié)點(diǎn),,可以通過(guò) id、class 或者 tagname 等等,,例如:
element.addEventListener('click', function(event) {
// 判斷是否是 a 節(jié)點(diǎn)
if ( event.target.tagName == 'A' ) {
// a 的一些交互操作
}
}, false);
停止事件冒泡(stopPropagation)
所有的事情都會(huì)有對(duì)立面,,事件的冒泡階段雖然看起來(lái)很好,也會(huì)有不適合的場(chǎng)所,。比較復(fù)雜的應(yīng)用,,由于事件監(jiān)聽(tīng)比較復(fù)雜,可能會(huì)希望只監(jiān)聽(tīng)發(fā)生在具體節(jié)點(diǎn)的事件,。這個(gè)時(shí)候就需要停止事件冒泡,。
停止事件冒泡需要使用事件對(duì)象的 stopPropagation 方法,具體代碼如下:
element.addEventListener('click', function(event) {
event.stopPropagation();
}, false);
在事件監(jiān)聽(tīng)的回調(diào)函數(shù)里,,會(huì)傳遞一個(gè)參數(shù),,這就是 Event 對(duì)象,在這個(gè)對(duì)象上調(diào)用 stopPropagation 方法即可停止事件冒泡,。舉個(gè)停止事件冒泡的應(yīng)用實(shí)例:
JS Bin
在上面例子中,,有一個(gè)彈出層,,我們可以在彈出層上做任何操作,例如 click 等,。當(dāng)我們想關(guān)掉這個(gè)彈出層,,在彈出層外面的任意結(jié)構(gòu)中點(diǎn)擊即可關(guān)掉。它首先對(duì) document 節(jié)點(diǎn)進(jìn)行 click 事件監(jiān)聽(tīng),,所有的 click 事件,,都會(huì)讓彈出層隱藏掉。同樣的,,我們?cè)趶棾鰧由厦娴膯螕舨僮饕矔?huì)導(dǎo)致彈出層隱藏,。之后我們對(duì)彈出層使用停止事件冒泡,掐斷了單擊事件返回 document 的冒泡路線,,這樣在彈出層的操作就不會(huì)被 document 的事件處理函數(shù)監(jiān)聽(tīng)到,。
更多關(guān)于 Event 對(duì)象的事情,我們會(huì)在下面介紹,。
事件的 Event 對(duì)象
當(dāng)一個(gè)事件被觸發(fā)的時(shí)候,,會(huì)創(chuàng)建一個(gè)事件對(duì)象(Event Object),這個(gè)對(duì)象里面包含了一些有用的屬性或者方法,。事件對(duì)象會(huì)作為第一個(gè)參數(shù),,傳遞給我們的毀掉函數(shù)。我們可以使用下面代碼,,在瀏覽器中打印出這個(gè)事件對(duì)象:
<button>打印 Event Object</button>
<script>
var btn = document.getElementsByTagName('button');
btn[0].addEventListener('click', function(event) {
console.log(event);
}, false);
</script>
就可以看到一堆屬性列表:
事件對(duì)象包括很多有用的信息,,比如事件觸發(fā)時(shí),鼠標(biāo)在屏幕上的坐標(biāo),、被觸發(fā)的 DOM 詳細(xì)信息,、以及上圖最下面繼承過(guò)來(lái)的停止冒泡方法(stopPropagation)。下面介紹一下比較常用的幾個(gè)屬性和方法:
type (string)
事件的名稱,,比如 “click”,。
target (node)
事件要觸發(fā)的目標(biāo)節(jié)點(diǎn)。
bubbles (boolean)
表明該事件是否是在冒泡階段觸發(fā)的,。
preventDefault (function)
這個(gè)方法可以禁止一切默認(rèn)的行為,,例如點(diǎn)擊 a 標(biāo)簽時(shí),會(huì)打開(kāi)一個(gè)新頁(yè)面,,如果為 a 標(biāo)簽監(jiān)聽(tīng)事件 click 同時(shí)調(diào)用該方法,則不會(huì)打開(kāi)新頁(yè)面,。
stopPropagation (function)
停止冒泡,,上面有提到,不再贅述,。
stopImmediatePropagation (function)
與 stopPropagation 類似,,就是阻止觸發(fā)其他監(jiān)聽(tīng)函數(shù),。但是與 stopPropagation 不同的是,它更加 “強(qiáng)力”,,阻止除了目標(biāo)之外的事件觸發(fā),,甚至阻止針對(duì)同一個(gè)目標(biāo)節(jié)點(diǎn)的相同事件,Demo:http:///yujiangshui/ju2ujmzp/2/,。
cancelable (boolean)
這個(gè)屬性表明該事件是否可以通過(guò)調(diào)用 event.preventDefault 方法來(lái)禁用默認(rèn)行為,。
eventPhase (number)
這個(gè)屬性的數(shù)字表示當(dāng)前事件觸發(fā)在什么階段。none:0,;捕獲:1,;目標(biāo):2;冒泡:3,。
pageX 和 pageY (number)
這兩個(gè)屬性表示觸發(fā)事件時(shí),,鼠標(biāo)相對(duì)于頁(yè)面的坐標(biāo)。Demo:http://api./event.pagex/,。
isTrusted (boolean)
表明該事件是瀏覽器觸發(fā)(用戶真實(shí)操作觸發(fā)),,還是 JavaScript 代碼觸發(fā)的。
jQuery 中的事件
如果你在寫文章或者 Demo,,為了簡(jiǎn)單,,你當(dāng)然可以用上面的事件監(jiān)聽(tīng)函數(shù),以及那些事件對(duì)象提供的方法等,。但在實(shí)際中,,有一些方法和屬性是有兼容性問(wèn)題的,所以我們會(huì)使用 jQuery 來(lái)消除兼容性問(wèn)題,。
下面簡(jiǎn)單的來(lái)說(shuō)一下 jQuery 中事件的基礎(chǔ)操作,。
綁定事件和事件代理
在 jQuery 中,提供了諸如 click() 這樣的語(yǔ)法糖來(lái)綁定對(duì)應(yīng)事件,,但是這里推薦統(tǒng)一使用 on() 來(lái)綁定事件,。語(yǔ)法:
.on( events [, selector ] [, data ], handler )
events 即為事件的名稱,你可以傳遞第二個(gè)參數(shù)來(lái)實(shí)現(xiàn)事件代理,,具體文檔.on() 這里不再贅述,。
處理過(guò)兼容性的事件對(duì)象(Event Object)
事件對(duì)象有些方法等也有兼容性差異,jQuery 將其封裝處理,,并提供跟標(biāo)準(zhǔn)一直的命名,。
如果你想在 jQuery 事件回調(diào)函數(shù)中訪問(wèn)原來(lái)的事件對(duì)象,需要使用 event.originalEvent ,,它指向原生的事件對(duì)象,。
觸發(fā)事件 trigger 方法
點(diǎn)擊某個(gè)綁定了 click 事件的節(jié)點(diǎn),自然會(huì)觸發(fā)該節(jié)點(diǎn)的 click 事件,從而執(zhí)行對(duì)應(yīng)回調(diào)函數(shù),。
trigger 方法可以模擬觸發(fā)事件,,我們單擊另一個(gè)節(jié)點(diǎn) elementB,可以使用:
$(elementB).on('click', function(){
$(elementA).trigger( "click" );
});
來(lái)觸發(fā) elementA 節(jié)點(diǎn)的單擊監(jiān)聽(tīng)回調(diào)函數(shù),。詳情請(qǐng)看文檔 .trigger(),。
事件進(jìn)階話題
IE 瀏覽器的差異和兼容性問(wèn)題
IE 瀏覽器就是特立獨(dú)行,它對(duì)于事件的操作與標(biāo)準(zhǔn)有一些差異,。不過(guò) IE 瀏覽器現(xiàn)在也開(kāi)始慢慢努力改造,,讓瀏覽器變得更加標(biāo)準(zhǔn)。
IE 下綁定事件
在 IE 下面綁定一個(gè)事件監(jiān)聽(tīng),,在 IE9- 無(wú)法使用標(biāo)準(zhǔn)的 addEventListener 函數(shù),,而是使用自家的attachEvent ,具體用法:
element.attachEvent(<event-name>, <callback>);
其中 <event-name> 參數(shù)需要注意,,它需要為事件名稱添加 on 前綴,,比如有個(gè)事件叫 click ,標(biāo)準(zhǔn)事件監(jiān)聽(tīng)函數(shù)監(jiān)聽(tīng) click ,,IE 這里需要監(jiān)聽(tīng) onclick ,。
另一個(gè),它沒(méi)有第三個(gè)參數(shù),,也就是說(shuō)它只支持監(jiān)聽(tīng)在冒泡階段觸發(fā)的事件,,所以為了統(tǒng)一,在使用標(biāo)準(zhǔn)事件監(jiān)聽(tīng)函數(shù)的時(shí)候,,第三參數(shù)傳遞 false,。
當(dāng)然,這個(gè)方法在 IE9 已經(jīng)被拋棄,,在 IE11 已經(jīng)被移除了,,IE 也在慢慢變好。
IE 中 Event 對(duì)象需要注意的地方
IE 中往回調(diào)函數(shù)中傳遞的事件對(duì)象與標(biāo)準(zhǔn)也有一些差異,,你需要使用 window.event 來(lái)獲取事件對(duì)象,。所以你通常會(huì)寫出下面代碼來(lái)獲取事件對(duì)象:
event = event || window.event
此外還有一些事件屬性有差別,比如比較常用的 event.target 屬性,,IE 中沒(méi)有,,而是使用 event.srcElement 來(lái)代替。如果你的回調(diào)函數(shù)需要處理觸發(fā)事件的節(jié)點(diǎn),,那么需要寫:
node = event.srcElement || event.target;
常見(jiàn)的就是這點(diǎn),,更細(xì)節(jié)的不再多說(shuō)。在概念學(xué)習(xí)中,,我們沒(méi)必要為不標(biāo)準(zhǔn)的東西支付學(xué)習(xí)成本,;在實(shí)際應(yīng)用中,類庫(kù)已經(jīng)幫我們封裝好這些兼容性問(wèn)題??上驳氖?IE 瀏覽器現(xiàn)在也開(kāi)始不斷向標(biāo)準(zhǔn)進(jìn)步。
事件回調(diào)函數(shù)的作用域問(wèn)題
與事件綁定在一起的回調(diào)函數(shù)作用域會(huì)有問(wèn)題,,我們來(lái)看個(gè)例子:
Events in JavaScript: Removing event listeners
回調(diào)函數(shù)調(diào)用的 user.greeting 函數(shù)作用域應(yīng)該是在 user 下的,,本期望輸出 My name is Bob 結(jié)果卻輸出了My name is undefined 。這是因?yàn)槭录壎ê瘮?shù)時(shí),,該函數(shù)會(huì)以當(dāng)前元素為作用域執(zhí)行,。為了證明這一點(diǎn),我們可以為當(dāng)前 element 添加屬性:
element.firstname = 'jiangshui';
再次點(diǎn)擊,,可以正確彈出 My name is jiangshui ,。那么我們來(lái)解決一下這個(gè)問(wèn)題。
使用匿名函數(shù)
我們?yōu)榛卣{(diào)函數(shù)包裹一層匿名函數(shù),。
Events in JavaScript: Removing event listeners
包裹之后,,雖然匿名函數(shù)的作用域被指向事件觸發(fā)元素,但執(zhí)行的內(nèi)容就像直接調(diào)用一樣,,不會(huì)影響其作用域,。
使用 bind 方法
使用匿名函數(shù)是有缺陷的,每次調(diào)用都包裹進(jìn)匿名函數(shù)里面,,增加了冗余代碼等,,此外如果想使用removeEventListener 解除綁定,還需要再創(chuàng)建一個(gè)函數(shù)引用,。Function 類型提供了 bind 方法,,可以為函數(shù)綁定作用域,無(wú)論函數(shù)在哪里調(diào)用,,都不會(huì)改變它的作用域,。通過(guò)如下語(yǔ)句綁定作用域:
user.greeting = user.greeting.bind(user);
這樣我們就可以直接使用:
element.addEventListener('click', user.greeting);
常用事件和技巧
用戶的操作有很多種,所以有很多事件,。為了開(kāi)發(fā)方便,,瀏覽器又提供了一些事件,所以有很多很多的事件,。這里只介紹幾種常用的事件和使用技巧,。
load
load 事件在資源加載完成時(shí)觸發(fā)。這個(gè)資源可以是圖片,、CSS 文件,、JS 文件、視頻,、document 和 window 等等,。
比較常用的就是監(jiān)聽(tīng) window 的 load 事件,當(dāng)頁(yè)面內(nèi)所有資源全部加載完成之后就會(huì)觸發(fā)。比如用 JS 對(duì)圖片以及其他資源處理,,我們?cè)?nbsp;load 事件中觸發(fā),,可以保證 JS 不會(huì)在資源未加載完成就開(kāi)始處理資源導(dǎo)致報(bào)錯(cuò)。
同樣的,,也可以監(jiān)聽(tīng)圖片等其他資源加載情況,。
beforeunload
當(dāng)瀏覽者在頁(yè)面上的輸入框輸入一些內(nèi)容時(shí),未保存,、誤操作關(guān)掉網(wǎng)頁(yè)可能會(huì)導(dǎo)致輸入信息丟失,。
當(dāng)瀏覽者輸入信息但未保存時(shí)關(guān)掉網(wǎng)頁(yè),我們就可以開(kāi)始監(jiān)聽(tīng)這個(gè)事件,,例如:
window.addEventListener("beforeunload", function( event ) {
event.returnValue = "放棄當(dāng)前未保存內(nèi)容而關(guān)閉頁(yè)面,?";
});
這時(shí)候試圖關(guān)閉網(wǎng)頁(yè)的時(shí)候,會(huì)彈窗阻止操作,,點(diǎn)擊確認(rèn)之后才會(huì)關(guān)閉,。當(dāng)然,如果沒(méi)有必要,,就不要監(jiān)聽(tīng),,不要以為使用它可以為你留住瀏覽者。
resize
當(dāng)節(jié)點(diǎn)尺寸發(fā)生變化時(shí),,觸發(fā)這個(gè)事件,。通常用在 window 上,這樣可以監(jiān)聽(tīng)瀏覽器窗口的變化,。通常用在復(fù)雜布局和響應(yīng)式上,。
常見(jiàn)的視差滾動(dòng)效果網(wǎng)站以及同類比較復(fù)雜的布局網(wǎng)站,往往使用 JavaScript 來(lái)計(jì)算尺寸,、位置,。如果用戶調(diào)整瀏覽器大小,尺寸,、位置不隨著改變則會(huì)出現(xiàn)錯(cuò)位情況,。在 window 上監(jiān)聽(tīng)該事件,觸發(fā)時(shí)調(diào)用計(jì)算尺寸,、位置的函數(shù),,可以根據(jù)瀏覽器的大小來(lái)重新計(jì)算。
但需要注意一點(diǎn),,當(dāng)瀏覽器發(fā)生任意變化都會(huì)觸發(fā) resize 事件,,哪怕是縮小 1px 的瀏覽器寬度,這樣調(diào)整瀏覽器時(shí)會(huì)觸發(fā)大量的 resize 事件,,你的回調(diào)函數(shù)就會(huì)被大量的執(zhí)行,,導(dǎo)致變卡,、崩潰等。
你可以使用函數(shù) throttle 或者 debounce 技巧來(lái)進(jìn)行優(yōu)化,,throttle 方法大體思路就是在某一段時(shí)間內(nèi)無(wú)論多次調(diào)用,,只執(zhí)行一次函數(shù),到達(dá)時(shí)間就執(zhí)行,;debounce 方法大體思路就是在某一段時(shí)間內(nèi)等待是否還會(huì)重復(fù)調(diào)用,,如果不會(huì)再調(diào)用,就執(zhí)行函數(shù),,如果還有重復(fù)調(diào)用,則不執(zhí)行繼續(xù)等待,。關(guān)于它們更詳細(xì)的信息,,我后面會(huì)介紹一下發(fā)表在我的博客上,這里不再贅述,。
error
當(dāng)我們加載資源失敗或者加載成功但是只加載一部分而無(wú)法使用時(shí),,就會(huì)觸發(fā) error 事件,我們可以通過(guò)監(jiān)聽(tīng)該事件來(lái)提示一個(gè)友好的報(bào)錯(cuò)或者進(jìn)行其他處理,。比如 JS 資源加載失敗,,則提示嘗試刷新;圖片資源加載失敗,,在圖片下面提示圖片加載失敗等,。該事件不會(huì)冒泡。因?yàn)樽庸?jié)點(diǎn)加載失敗,,并不意味著父節(jié)點(diǎn)加載失敗,,所以你的處理函數(shù)必須精確綁定到目標(biāo)節(jié)點(diǎn)。
需要注意的是,,對(duì)于該事件,,你可以使用 addEventListener 等進(jìn)行監(jiān)聽(tīng),但是有時(shí)候會(huì)出現(xiàn)失效情況(看這個(gè)例子),,這是因?yàn)?nbsp;error 事件都觸發(fā)過(guò)了,,你的 JS 監(jiān)聽(tīng)處理代碼還沒(méi)有加載進(jìn)來(lái)執(zhí)行。為了避免這種情況,,用內(nèi)聯(lián)法更好一些:
<img src="not-found.jpg" onerror="doSomething" />
如果還有其他常用事件,,歡迎留言補(bǔ)充。
用 JavaScript 模擬觸發(fā)內(nèi)置事件
內(nèi)置的事件也可以被 JavaScript 模擬觸發(fā),,比如下面函數(shù)模擬觸發(fā)單擊事件:
function simulateClick() {
var event = new MouseEvent('click', {
'view': window,
'bubbles': true,
'cancelable': true
});
var cb = document.getElementById('checkbox');
var canceled = !cb.dispatchEvent(event);
if (canceled) {
// A handler called preventDefault.
alert("canceled");
} else {
// None of the handlers called preventDefault.
alert("not canceled");
}
}
可以看這個(gè) Demo 來(lái)了解更多,。
自定義事件
我們可以自定義事件來(lái)實(shí)現(xiàn)更靈活的開(kāi)發(fā),事件用好了可以是一件很強(qiáng)大的工具,,基于事件的開(kāi)發(fā)有很多優(yōu)勢(shì)(后面介紹),。
與自定義事件的函數(shù)有 Event ,、CustomEvent 和 dispatchEvent 。
直接自定義事件,,使用 Event 構(gòu)造函數(shù):
var event = new Event('build');
// Listen for the event.
elem.addEventListener('build', function (e) { ... }, false);
// Dispatch the event.
elem.dispatchEvent(event);
CustomEvent 可以創(chuàng)建一個(gè)更高度自定義事件,,還可以附帶一些數(shù)據(jù),具體用法如下:
var myEvent = new CustomEvent(eventname, options);
其中 options 可以是:
{
detail: {
...
},
bubbles: true,
cancelable: false
}
其中 detail 可以存放一些初始化的信息,,可以在觸發(fā)的時(shí)候調(diào)用,。其他屬性就是定義該事件是否具有冒泡等等功能。
內(nèi)置的事件會(huì)由瀏覽器根據(jù)某些操作進(jìn)行觸發(fā),,自定義的事件就需要人工觸發(fā),。dispatchEvent 函數(shù)就是用來(lái)觸發(fā)某個(gè)事件:
element.dispatchEvent(customEvent);
上面代碼表示,在 element 上面觸發(fā) customEvent 這個(gè)事件,。結(jié)合起來(lái)用就是:
// add an appropriate event listener
obj.addEventListener("cat", function(e) { process(e.detail) });
// create and dispatch the event
var event = new CustomEvent("cat", {"detail":{"hazcheeseburger":true}});
obj.dispatchEvent(event);
使用自定義事件需要注意兼容性問(wèn)題,,而使用 jQuery 就簡(jiǎn)單多了:
// 綁定自定義事件
$(element).on('myCustomEvent', function(){});
// 觸發(fā)事件
$(element).trigger('myCustomEvent');
此外,你還可以在觸發(fā)自定義事件時(shí)傳遞更多參數(shù)信息:
$( "p" ).on( "myCustomEvent", function( event, myName ) {
$( this ).text( myName + ", hi there!" );
});
$( "button" ).click(function () {
$( "p" ).trigger( "myCustomEvent", [ "John" ] );
});
更詳細(xì)的用法請(qǐng)看 Introducing Custom Events,,這里不再贅述,。
在開(kāi)發(fā)中應(yīng)用事件
當(dāng)我們操作某一個(gè) DOM,發(fā)出一個(gè)事件,,我們可以在另一個(gè)地方寫代碼捕獲這個(gè)事件執(zhí)行處理邏輯,。觸發(fā)操作和捕獲處理操作是分開(kāi)的。我們可以根據(jù)這個(gè)特性來(lái)對(duì)程序解耦,。
用事件解耦
我們可以將一個(gè)整個(gè)的功能,,分割成獨(dú)立的小功能,每個(gè)小功能綁定一個(gè)事件,,由一個(gè)“控制器”負(fù)責(zé)根據(jù)條件觸發(fā)某個(gè)事件,。這樣,在外面觸發(fā)這個(gè)事件,,也可以調(diào)用對(duì)應(yīng)功能,,使其更加靈活。
在《基于 MVC 的 JavaScript Web 富應(yīng)用開(kāi)發(fā)》一書(shū)中,,有更加具體的實(shí)例,,有興趣的朋友可以買本看看。
發(fā)布(Publish)和訂閱(Subscribe)模式
針對(duì)上面這種用法,,繼續(xù)抽象一下,,就是發(fā)布和訂閱開(kāi)發(fā)模式。正如其名,,這種模式有兩個(gè)角色:發(fā)布者和訂閱者,,此外有一條信道,發(fā)布者被觸發(fā)往這個(gè)信道里面發(fā)信,,訂閱者從這個(gè)信道里面收信,,如果收到特定信件則執(zhí)行某個(gè)對(duì)應(yīng)的邏輯,。這樣,發(fā)布者和訂閱者之間是完全解耦的,,只有一條信道連接,。這樣就非常容易擴(kuò)展,也不會(huì)引入額外的依賴,。
這樣如果需要添加新功能,,只需要添加一個(gè)新的訂閱者(及其執(zhí)行邏輯),監(jiān)聽(tīng)信道中某一類新的信件,。再在應(yīng)用中通過(guò)發(fā)布者發(fā)送一類新的信件即可,。
具體實(shí)現(xiàn),這里推薦 cowboy 開(kāi)發(fā)的 Tiny Pub Sub,,通過(guò) jQuery 實(shí)現(xiàn),,非常簡(jiǎn)潔直觀,jQuery 太贊,。代碼就這幾行:
(function($) {
var o = $({});
$.subscribe = function() {
o.on.apply(o, arguments);
};
$.unsubscribe = function() {
o.off.apply(o, arguments);
};
$.publish = function() {
o.trigger.apply(o, arguments);
};
}(jQuery));
定義一個(gè)對(duì)象作為信道,然后提供了三個(gè)方法,,訂閱者,、取消訂閱、發(fā)布者,。
總結(jié)和擴(kuò)展閱讀
事件有關(guān)的基礎(chǔ)知識(shí)基本就這些,,更多的還有待你繼續(xù)挖掘。本文資料參考和推薦擴(kuò)展閱讀如下(感謝他們):
|