NARUTOne
引言:js的事件機(jī)制在web開發(fā)中的出鏡率是很高,但是要想給事件機(jī)制拍出高顏值的效果歼冰,還是很難的靡狞。本篇將會(huì)介紹javascript事件機(jī)制這回事兒。
事件有哪些
//獲取window事件
var log = document.getElementById('log'),
i = '',
out = [];
for (i in window) {
if ( /^on/.test(i)) { out[out.length] = i; }
}
log.innerHTML = out.join(', ');
可以發(fā)現(xiàn)甸怕,事件家族成員還是很龐大的。
事件綁定監(jiān)聽
html內(nèi)聯(lián)綁定
<a href='javascript:;' onclick="alert('你點(diǎn)擊了這個(gè)a');">點(diǎn)擊</a>
顯而易見腮恩,使用這種方法梢杭,JavaScript 代碼與 HTML 代碼耦合在了一起,不便于維護(hù)和開發(fā)秸滴。所以除非在必須使用的情況(例如統(tǒng)計(jì)鏈接點(diǎn)擊數(shù)據(jù))下武契,盡量避免使用這種方法。
DOM屬性綁定
element.onclick = function(event){
alert('你點(diǎn)擊了這個(gè)按鈕');
};
直接賦值給對(duì)應(yīng)屬性荡含,如果你在后面代碼中再次為 element 綁定一個(gè)回調(diào)函數(shù)吝羞,會(huì)覆蓋掉之前回調(diào)函數(shù)的內(nèi)容。
事件監(jiān)聽
element.addEventListener(<event-name>, <callback>, <use-capture>);
表示在 element 這個(gè)對(duì)象上面添加一個(gè)事件監(jiān)聽器内颗。
1钧排、當(dāng)監(jiān)聽到有 <event-name> 事件發(fā)生的時(shí)候。
2均澳、調(diào)用 <callback> 這個(gè)回調(diào)函數(shù)恨溜。
3、至于 <use-capture> 這個(gè)參數(shù)找前,表示該事件監(jiān)聽是在“捕獲”階段中監(jiān)聽(設(shè)置為 true)還是在“冒泡”階段中監(jiān)聽(設(shè)置為 false)糟袁。大部分設(shè)置在冒泡階段。
var btn = document.getElementsByTagName('button');
btn[0].addEventListener('click', function() {
alert('你點(diǎn)擊了這個(gè)按鈕');
}, false);
移除監(jiān)聽
element.removeEventListener(<event-name>, <callback>, <use-capture>);
需要注意的是躺盛,綁定事件時(shí)的回調(diào)函數(shù)不能是匿名函數(shù)项戴,必須是一個(gè)聲明的函數(shù),因?yàn)榻獬录壎〞r(shí)需要傳遞這個(gè)回調(diào)函數(shù)的引用槽惫,才可以斷開綁定周叮。
var fun = function() {
// function logic
};
element.addEventListener('click', fun, false);
element.removeEventListener('click', fun, false);
add: addEventListener是用來注冊(cè)事件的辩撑,通常第二個(gè)參數(shù)我們傳入的是一個(gè)回調(diào)函數(shù);但是也可以傳一個(gè)object,只需要這個(gè)object具有handleEvent屬性即可仿耽。
事件觸發(fā)過程
事件綁定后合冀,那事件發(fā)生時(shí)是如何確認(rèn)發(fā)生對(duì)象和觸發(fā)的呢,過程詳看w3c UI Events
捕獲階段(Capture Phase)
當(dāng)我們?cè)?DOM 樹的某個(gè)節(jié)點(diǎn)發(fā)生了一些操作(例如單擊项贺、鼠標(biāo)移動(dòng)上去)君躺,就會(huì)有一個(gè)事件發(fā)射過去。這個(gè)事件從 Window 發(fā)出开缎,不斷經(jīng)過下級(jí)節(jié)點(diǎn)直到目標(biāo)節(jié)點(diǎn)棕叫。在到達(dá)目標(biāo)節(jié)點(diǎn)之前的過程,就是捕獲階段(Capture Phase)奕删。
所有經(jīng)過的節(jié)點(diǎn)俺泣,都會(huì)觸發(fā)這個(gè)事件。捕獲階段的任務(wù)就是建立這個(gè)事件傳遞路線急侥,以便后面冒泡階段順著這條路線返回 Window砌滞。
監(jiān)聽某個(gè)在捕獲階段觸發(fā)的事件,需要在事件監(jiān)聽函數(shù)傳遞第三個(gè)參數(shù) true坏怪。
element.addEventListener(<event-name>, <callback>, true);
目標(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)上横朋。
document.addEventListener('click', function(e){
alert(e.target.tagName);
}, false);
冒泡階段(Bubbling Phase)
當(dāng)事件達(dá)到目標(biāo)節(jié)點(diǎn)之后,就會(huì)沿著原路返回百拓,由于這個(gè)過程類似水泡從底部浮到頂部琴锭,所以稱作冒泡階段。
在實(shí)際使用中衙传,你并不需要把事件監(jiān)聽函數(shù)準(zhǔn)確綁定到最底層的節(jié)點(diǎn)也可以正常工作决帖。比如在上例,你想為這個(gè)div 綁定單擊時(shí)的回調(diào)函數(shù)蓖捶,你無須為這個(gè) div 下面的所有子節(jié)點(diǎn)全部綁定單擊事件地回,只需要為 div 這一個(gè)節(jié)點(diǎn)綁定即可。因?yàn)榘l(fā)生它子節(jié)點(diǎn)的單擊事件,都會(huì)冒泡上去刻像,發(fā)生在 div 上面畅买。
事件機(jī)制FQA
為什么不用第三個(gè)參數(shù) true(捕獲)
在使用 addEventListener 函數(shù)來監(jiān)聽事件時(shí),第三個(gè)參數(shù)設(shè)置為 false绎速,這樣監(jiān)聽事件時(shí)只會(huì)監(jiān)聽冒泡階段發(fā)生的事件皮获。
這是因?yàn)?IE 瀏覽器不支持在捕獲階段監(jiān)聽事件焙蚓,為了統(tǒng)一而設(shè)置的纹冤,畢竟 IE 瀏覽器的份額是不可忽略的。
利用事件代理監(jiān)聽提升性能 (Event Delegate)
因?yàn)槭录忻芭輽C(jī)制购公,所有子節(jié)點(diǎn)的事件都會(huì)順著父級(jí)節(jié)點(diǎn)跑回去萌京,所以我們可以通過監(jiān)聽父級(jí)節(jié)點(diǎn)來實(shí)現(xiàn)監(jiān)聽子節(jié)點(diǎn)的功能,這就是事件代理宏浩。
使用事件代理主要有兩個(gè)優(yōu)勢(shì):
- 減少事件綁定知残,提升性能。之前你需要綁定一堆子節(jié)點(diǎn)比庄,而現(xiàn)在你只需要綁定一個(gè)父節(jié)點(diǎn)即可求妹。減少了綁定事件監(jiān)聽函數(shù)的數(shù)量。
- 動(dòng)態(tài)變化的 DOM 結(jié)構(gòu)佳窑,仍然可以監(jiān)聽制恍。當(dāng)一個(gè) DOM 動(dòng)態(tài)創(chuàng)建之后,不會(huì)帶有任何事件監(jiān)聽神凑,除非你重新執(zhí)行事件監(jiān)聽函數(shù)净神,而使用事件監(jiān)聽無須擔(dān)憂這個(gè)問題。
<ul id="resources">
<li><a >MDN</a></li>
<li><a >HTML5 Doctor</a></li>
<li><a >HTML5 Rocks</a></li>
<li><a >Expressive Web</a></li>
<li><a >CreativeJS</a></li>
</ul>
var resources = document.querySelector('#resources'),
log = document.querySelector('#log');
resources.addEventListener('mouseover', showtarget, false);
function showtarget(ev) {
var target = ev.target;
if (target.tagName === 'A') {
log.innerHTML = 'A link, with the href:' + target.href;
}
if (target.tagName === 'LI') {
log.innerHTML = 'A list item';
}
if (target.tagName === 'UL') {
log.innerHTML = 'The list itself';
}
}
使用原生的方式實(shí)現(xiàn)事件代理溉委,需要注意過濾非目標(biāo)節(jié)點(diǎn)
element.addEventListener('click', function(event) {
// 判斷是否是 a 節(jié)點(diǎn)
if ( event.target.tagName == 'A' ) {
// a 的一些交互操作
}
}, false);
停止事件冒泡(stopPropagation)
比較復(fù)雜的應(yīng)用鹃唯,由于事件監(jiān)聽比較復(fù)雜,可能會(huì)希望只監(jiān)聽發(fā)生在具體節(jié)點(diǎn)的事件瓣喊。這個(gè)時(shí)候就需要停止事件冒泡坡慌。停止事件冒泡需要使用事件對(duì)象的 stopPropagation 方法
element.addEventListener('click', function(event) {
event.stopPropagation();
}, false);
事件對(duì)象
當(dāng)一個(gè)事件被觸發(fā)的時(shí)候,會(huì)創(chuàng)建一個(gè)事件對(duì)象(Event Object)藻三,這個(gè)對(duì)象里面包含了一些有用的屬性或者方法洪橘。事件對(duì)象會(huì)作為第一個(gè)參數(shù),傳遞給我們的毀掉函數(shù)趴酣。我們可以使用下面代碼梨树,在瀏覽器中打印出這個(gè)事件對(duì)象:標(biāo)準(zhǔn)事件屬性列表
<button>打印 Event Object</button>
<script>
var btn = document.getElementsByTagName('button');
btn[0].addEventListener('click', function(event) {
console.log(event);
}, false);
</script>
默認(rèn)事件禁止preventDefault
這個(gè)方法可以禁止一切默認(rèn)的行為,例如點(diǎn)擊 a 標(biāo)簽時(shí)岖寞,會(huì)打開一個(gè)新頁面抡四,如果為 a 標(biāo)簽監(jiān)聽事件 click同時(shí)調(diào)用該方法,則不會(huì)打開新頁面。
<a href="www.baidu.com" id='jump'>是否跳轉(zhuǎn)呢指巡?</a>
document.getElementById('jump').addEventListener('click', function(e) {
alert('會(huì)跳轉(zhuǎn)到www.baidu.com淑履??藻雪?');
e.preventDefault();
}, false)
IE下的事件差異兼容
IE 下綁定事件
在 IE 下面綁定一個(gè)事件監(jiān)聽秘噪,在 IE9- 無法使用標(biāo)準(zhǔn)的 addEventListener 函數(shù),而是使用自家的attachEvent勉耀,具體用法:
element.attachEvent(<event-name>, <callback>);
其中 <event-name> 參數(shù)需要注意指煎,它需要為事件名稱添加 on 前綴,比如有個(gè)事件叫 click便斥,標(biāo)準(zhǔn)事件監(jiān)聽函數(shù)監(jiān)聽 click至壤,IE 這里需要監(jiān)聽 onclick。
另一個(gè)枢纠,它沒有第三個(gè)參數(shù)像街,也就是說它只支持監(jiān)聽在冒泡階段觸發(fā)的事件,所以為了統(tǒng)一晋渺,在使用標(biāo)準(zhǔn)事件監(jiān)聽函數(shù)的時(shí)候镰绎,第三參數(shù)傳遞 false。
當(dāng)然木西,這個(gè)方法在 IE9 已經(jīng)被拋棄畴栖,在 IE11 已經(jīng)被移除了,IE 也在慢慢變好户魏。O(∩_∩)O~
** IE中的Event **
IE 中往回調(diào)函數(shù)中傳遞的事件對(duì)象與標(biāo)準(zhǔn)也有一些差異驶臊,你需要使用 window.event 來獲取事件對(duì)象。所以你通常會(huì)寫出下面代碼來獲取事件對(duì)象:
event = event || window.event
此外還有一些事件屬性有差別叼丑,比如比較常用的 event.target 屬性关翎,IE 中沒有,而是使用 event.srcElement來代替鸠信。如果你的回調(diào)函數(shù)需要處理觸發(fā)事件的節(jié)點(diǎn)纵寝,那么需要寫:
node = event.srcElement || event.target;
用 JavaScript 模擬觸發(fā)內(nèi)置事件
內(nèi)置的事件也可以被 JavaScript 模擬觸發(fā),dispatchEvent()
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");
}
}
自定義事件
內(nèi)置的事件會(huì)由瀏覽器根據(jù)某些操作進(jìn)行觸發(fā)星立,自定義的事件就需要人工觸發(fā)爽茴。dispatchEvent 函數(shù)就是用來觸發(fā)某個(gè)事件
自定義事件的函數(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)建
CustomEvent 可以創(chuàng)建一個(gè)更高度自定義事件绰垂,還可以附帶一些數(shù)據(jù)室奏。
var myEvent = new CustomEvent(eventname, options);
var options = {
detail: {
...
},
bubbles: true,
cancelable: false
}
element.dispatchEvent(customEvent);
detail 可以存放一些初始化的信息,可以在觸發(fā)的時(shí)候調(diào)用劲装。其他屬性就是定義該事件是否具有冒泡等等功能胧沫。
在 element 上面觸發(fā) customEvent 這個(gè)事件昌简。結(jié)合起來用就是:
// 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);
使用自定義事件需要注意兼容性問題,而使用 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" ] );
});
事件解耦
我們可以將一個(gè)整個(gè)的功能纯赎,分割成獨(dú)立的小功能,每個(gè)小功能綁定一個(gè)事件南蹂,由一個(gè)“控制器”負(fù)責(zé)根據(jù)條件觸發(fā)某個(gè)事件犬金。這樣,在外面觸發(fā)這個(gè)事件六剥,也可以調(diào)用對(duì)應(yīng)功能晚顷,使其更加靈活。
這里推薦 cowboy 開發(fā)的 Tiny Pub Sub仗考,通過 jQuery 實(shí)現(xiàn)
(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));
媒體事件
video 和 audio這倆很潮的玩意也有一大堆事件供我們使用音同。比如有趣的time事件词爬,它可以告訴我們這首歌或電影的已播放時(shí)長(zhǎng)秃嗜。
其他設(shè)備事件
我們知道,瀏覽器提供了與鼠標(biāo)鍵盤的交互顿膨,但這還遠(yuǎn)遠(yuǎn)不夠滿足我們更多的硬件交互需求锅锨。比如檢測(cè)手機(jī)或平板電腦傾斜度的Device orientation和 touch events。the Gamepad API讓我們可以在瀏覽器中做游戲控制恋沃;postMessage讓我們可以在瀏覽器各窗口之間進(jìn)行跨域消息傳遞必搞;pageVisibility讓我們可以得知瀏覽器中當(dāng)前標(biāo)簽頁可見狀態(tài)。甚至當(dāng)window的history對(duì)象有操作時(shí)也能監(jiān)聽的到囊咏。查看window對(duì)象的事件列表恕洲,有的可能已經(jīng)被實(shí)現(xiàn)了,還有更多的在謀劃中……
嘛梅割,不管瀏覽器是否會(huì)支持霜第,最終都是要支持的嘛,這些是剛需户辞。我們只要默默等待就可以了泌类,騷年,向著夕陽奔跑吧底燎!=v=
總結(jié)
綁定刃榨、捕獲、冒泡双仍、委托及瀏覽器兼容枢希。
1、綁定三種方式:
①onEle = fun模式 :
②addEventListener('ele', fun, bool)模式:
③attachEvent('onEle', fun, bool)模式:
2朱沃、默認(rèn)事件行為阻止(一般):
①on模式:
ele.onclick = function() {
…… //你的代碼
return false; //通過返回false值阻止默認(rèn)事件行為
};
②addEventListener()模式:
element.addEventListener("click", function(e){
var event = e || window.event;
……
event.preventDefault( ); //阻止默認(rèn)事件
},false);
③attachEvent()模式 IE
element.attachEvent("onclick", function(e){
var event = e || window.event;
……
event.returnValue = false; //阻止默認(rèn)事件
},false);
3苞轿、兼容綁定、解綁
// 事件綁定
function addEvent(element, eType, handle, bol) {
if(element.addEventListener){ //如果支持addEventListener
element.addEventListener(eType, handle, bol);
}else if(element.attachEvent){ //如果支持attachEvent
element.attachEvent("on"+eType, handle);
}else{ //否則使用兼容的onclick綁定
element["on"+eType] = handle;
}
}
// 事件解綁
function removeEvent(element, eType, handle, bol) {
if(element.addEventListener){
element.removeEventListener(eType, handle, bol);
}else if(element.attachEvent){
element.detachEvent("on"+eType, handle);
}else{
element["on"+eType] = null;
}
}
4、冒泡
// 阻止事件的進(jìn)一步傳播呕屎,包括(冒泡让簿,捕獲),無參數(shù)
event.stopPropagation( );
event.cancelBubble = true; // true 為阻止冒泡秀睛,IE
5尔当、事件委托:利用事件冒泡的特性,將里層的事件委托給外層事件蹂安,根據(jù)event對(duì)象的屬性進(jìn)行事件委托椭迎,改善性能。
使用事件委托能夠避免對(duì)特定的每個(gè)節(jié)點(diǎn)添加事件監(jiān)聽器田盈;事件監(jiān)聽器是被添加到它們的父元素上畜号。事件監(jiān)聽器會(huì)分析從子元素冒泡上來的事件,找到是哪個(gè)子元素的事件允瞧。