JavaScript 是基于面向?qū)ο蠛褪录?qū)動(dòng)的一門語(yǔ)言,事件模型是 DOM 中至關(guān)重要的內(nèi)容,理解事件驅(qū)動(dòng)機(jī)制、事件反饋、事件冒泡、事件捕獲以及事件委托能幫助我們更好的處理事件,寫(xiě)出更優(yōu)的代碼
當(dāng)事件發(fā)生時(shí),我們收到事件的反饋,在 JavaScript 中,事件反饋是我們自行定義的事件處理函數(shù)
事件,如點(diǎn)擊事件、鼠標(biāo)移入事件等,是每一個(gè)元素與生俱來(lái)的能力
通常說(shuō)的綁定事件,實(shí)際上是綁定事件的反饋,即事件處理函數(shù)
例如點(diǎn)擊一個(gè)按鈕,按鈕元素對(duì)象是事件發(fā)送器或事件源,事件是鼠標(biāo)點(diǎn)擊事件,事件處理函數(shù)是偵聽(tīng)器
元素對(duì)象發(fā)出事件,事件處理函數(shù)做出反應(yīng),這就是 JS 的事件驅(qū)動(dòng)機(jī)制
在觀察者模式中,事件發(fā)送器就是主題,事件處理函數(shù)即偵聽(tīng)器就是觀察者
內(nèi)聯(lián)屬性
<button onclick="test()">按鈕</button>
介于結(jié)構(gòu)和邏輯要相分離,不建議使用內(nèi)聯(lián)方式綁定
事件句柄
var oBtn = document.getElementsByTagName('button')[0]; oBtn.onclick = function() { // this -> oBtn }
兼容性好,但是重復(fù)綁定會(huì)覆蓋
事件監(jiān)聽(tīng)器
var oBtn = document.getElementsByTagName('button')[0]; oBtn.addEventListener("click", funtion(){ // this -> oBtn }, false); oBtn.addEventListener("click", test, false); funtion test(){ // 事件處理函數(shù) }
重復(fù)添加,不會(huì)覆蓋之前添加的監(jiān)聽(tīng)器,但是如果事件類型、事件處理函數(shù)和最后一個(gè)布爾參數(shù)都相同,則不會(huì)重復(fù)執(zhí)行
IE8 及以下不支持 addEventListener,可用 attachEvent 代替
var oBtn = document.getElementsByTagName('button')[0]; oBtn.attachEvent("onclick", funtion(){ // this -> window }); // 區(qū)別于 addEventListener,第一個(gè)參數(shù)使用 'onclick',而不是 'click' // 并且內(nèi)部 this 指向 window // 對(duì)于 attachEvent,如果事件類型、事件處理函數(shù)都相同,還是會(huì)重復(fù)執(zhí)行
兼容性封裝
function addEvent(elem, type, fn) { if (elem.addEventListener) { elem.addEventListener(type, fn, false); } else if (elem.attachEvent) { elem.attachEvent('on' + type, function(ev) { fn.call(elem, ev); // call 兼容性比 bind 好 }); } else { elem['on' + type] = fn; } }
解除綁定
oBtn.onclik = null; oBtn.removeEventListener("click", test, false); // 解除 addEventListener oBtn.detachEvent('onclick', test); // 解除 attachEvent
示例:點(diǎn)擊一次后清除事件反饋
oBtn.onclik = function() { // ... this.onclick = null; } // 非嚴(yán)格模式 oBtn.addEventListener("click", funtion() { // ... this.removEventListener('cilck', arguments.callee, false); }, false); // 嚴(yán)格模式 oBtn.addEventListener("click", funtion temp() { // ... this.removeEventListener('click', temp, false); }, false);
事件冒泡:當(dāng)一個(gè)元素發(fā)生事件時(shí),該事件會(huì)向父級(jí)元素傳遞,按由子到父的順序觸發(fā)一連串的事件反饋,稱之為事件冒泡
DOM 上的嵌套關(guān)系會(huì)產(chǎn)生事件冒泡,例如兩個(gè) div 嵌套,點(diǎn)擊內(nèi)部的 div,觸發(fā)內(nèi)部 div 的點(diǎn)擊事件,內(nèi)部 div 的點(diǎn)擊事件處理函數(shù)進(jìn)行響應(yīng),這個(gè)事件向其父級(jí)即外部 div 傳遞,外部 div 也有點(diǎn)擊事件,外部 div 所綁定的點(diǎn)擊事件反饋也會(huì)響應(yīng)
<div class="outer"> <div class="inner"></div> </div>
var outer = document.getElementsByClassName('outer')[0], inner = outer.getElementsByClassName('inner')[0]; outer.addEventListener('click', function () { console.log('bubble outer'); }, false); inner.addEventListener('click', function () { console.log('bubble inner'); }, false); // addEventListener 最后一個(gè)參數(shù)默認(rèn)值為 false,表示事件冒泡 // 點(diǎn)擊 inner,打印出 // bubble inner // bubble outer
事件捕獲:當(dāng)一個(gè)元素發(fā)生事件時(shí),該事件會(huì)向父級(jí)元素傳遞,按由父到子的順序觸發(fā)一連串的事件反饋,稱之為事件捕獲
事件捕獲與事件冒泡的觸發(fā)順序相反,同樣需要 DOM 上的嵌套關(guān)系
outer.addEventListener('click', function () { console.log('outer'); }, true); inner.addEventListener('click', function () { console.log('inner'); }, true); // addEventListener 最后一個(gè)參數(shù)使用 true,表示事件捕獲 // 點(diǎn)擊 inner,打印出 // outer // in
捕獲和冒泡的執(zhí)行順序
outer.addEventListener('click', function () { console.log('bubble outer'); }, false); // 冒泡 inner.addEventListener('click', function () { console.log('bubble inner'); }, false); // 冒泡 outer.addEventListener('click', function () { console.log('outer'); }, true); // 捕獲 inner.addEventListener('click', function () { console.log('inner'); }, true); // 捕獲 // 點(diǎn)擊 inner,打印出 // outer // bubble inner // inner // bubble outer
點(diǎn)擊一個(gè)元素,元素即事件源,若事件源綁定了事件處理函數(shù),且設(shè)定了事件捕獲,則先執(zhí)行捕獲,捕獲執(zhí)行完畢后,按照綁定順序執(zhí)行該事件源綁定的事件,如果設(shè)定了事件冒泡,再執(zhí)行冒泡
focus blur change submit reset select 事件沒(méi)有冒泡和捕獲,IE 瀏覽器沒(méi)有事件捕獲
阻止冒泡的方法
Event 的原型上有 stopPropagation 方法,可以阻止冒泡,是 w3c 的規(guī)范
Event 的原型上有 cancleBubble 屬性,賦值為 true,可以阻止冒泡
addEventListener 綁定事件處理函數(shù),拿到事件對(duì)象
var outer = document.getElementsByClassName('outer')[0], inner = outer.getElementsByClassName('inner')[0]; inner.addEventListener('click', function (ev) { console.log(ev); // 事件對(duì)象 ev ev.stopPropagation(); // 阻止事件冒泡 }, false);
IE 瀏覽器沒(méi)有 stopPropagation 方法,可以使用 cancelBubble 屬性
注意:IE 瀏覽器中事件對(duì)象存放在 window.event 中。IE8 不支持 addEventListener 方法
// 封裝阻止冒泡的方法 function cancelBubble(ev) { if (ev.stopPropagation) { ev.stopPropagation(); } else ev.cancelBubble = true; // 兼容 IE8 及以下 } // 使用上文中封裝好的 addEvent 方法 function addEvent(elem, type, fn) { if (elem.addEventListener) { elem.addEventListener(type, fn); } else if (elem.attachEvent) { elem.attachEvent('on' + type, function (ev) { fn.call(elem, ev); }); } else { elem['on' + type] = fn; } } // 綁定事件處理函數(shù) var outer = document.getElementsByClassName('outer')[0], inner = outer.getElementsByClassName('inner')[0]; addEvent(inner, 'click', function (ev) { var ev = ev || window.event; // IE 兼容性寫(xiě)法 cancelBubble(ev); // 阻止冒泡 });
三種方法
事件對(duì)象 preventDefault() 方法,兼容 IE9 及以上
事件對(duì)象 returnValue = false,兼容 IE8 及以下
事件處理函數(shù) return false
兼容性寫(xiě)法
function preventDefaultEvent(ev) { if (ev.preventDefault) { ev.preventDefault(); } else ev.returnValue = false; // 兼容 IE8 及以下 }
右鍵菜單事件
document.oncontextmenu = function (ev) { var ev = ev || window.event; // 1. ev.preventDefault(); // IE9 及以上 // 2. ev.returnValue = false; // IE8 及以下 // 3. return false; }
a 標(biāo)簽跳轉(zhuǎn)事件
href 使用偽協(xié)議
<a href="javascript:void(0);">a 標(biāo)簽</a> <a href="javascript:;">a 標(biāo)簽</a> <a href="#">a 標(biāo)簽</a> <!--跳轉(zhuǎn)到當(dāng)前頁(yè)面頂部-->
onclick 事件 return false
<a href="http://www.baidu.com" onclick="return false">a 標(biāo)簽</a> <a href="http://www.baidu.com" onclick="return test(),false">a 標(biāo)簽</a> <!--第二個(gè)是利用了 “,” 分隔符會(huì)返回最后一個(gè)的特點(diǎn),與 test 方法無(wú)關(guān)-->
綁定事件處理函數(shù)
<!--內(nèi)聯(lián)綁定--> <a id='taga' href="http://www.baidu.com" onclick="return test()">a 標(biāo)簽</a> <!--句柄綁定--> <script> document.getElementById('taga').onclick = test; function test(ev) { var ev = ev || window.event; // 1. ev.preventDefault(); // IE9 及以上 // 2. ev.returnValue = false; // IE8 及以下 // 3. return false; } // 前兩種方式在使用內(nèi)聯(lián)屬性綁定時(shí),不需要在屬性上加 return,第三種則需要 </script>
表單的 action 屬性支持
javascript:
偽協(xié)議,onsubmit 或者提交按鈕點(diǎn)擊事件都可以綁定處理函數(shù),阻止提交的方法和阻止 a 標(biāo)簽跳轉(zhuǎn)的方法類似
事件流:描述從頁(yè)面中接收事件的順序
事件冒泡流:微軟 IE 提出,Event Bubbling
事件捕獲流:網(wǎng)景 Netscape 提出,Event Capturing
事件流三個(gè)階段:事件捕獲階段、處于目標(biāo)階段、事件冒泡階段
元素觸發(fā)事件時(shí),首先事件捕獲階段,由父到子的執(zhí)行事件處理函數(shù),然后處于目標(biāo)階段,該元素的事件處理函數(shù)按綁定順序執(zhí)行,最后事件冒泡階段,由子到父的執(zhí)行事件處理函數(shù)
事件即事件對(duì)象,可以由事件處理函數(shù)的參數(shù)拿到
IE8 及以下中事件對(duì)象存放在 window.event 中
// btn 按鈕元素 btn.onclick = function(ev) { var ev = ev || window.event; // IE8 兼容性寫(xiě)法 }
事件源即事件源對(duì)象,是發(fā)生事件的元素,即事件發(fā)送器,可以從事件對(duì)象中獲取
IE8 及以下只有 srcElement,firefox 低版本只有 target,chrome 兩者都有
// btn 按鈕元素 btn.onclick = function(ev) { var ev = ev || window.event; // IE8 兼容性寫(xiě)法 var tar = ev.target || ev.srcElement; // 獲取事件源的兼容性寫(xiě)法 }
事件委托也叫事件代理,指對(duì)父級(jí)元素綁定事件處理函數(shù),通過(guò)獲取事件源來(lái)處理子元素
示例:點(diǎn)擊按鈕使列表 ul 增加 li 元素,點(diǎn)擊每個(gè) li 元素打印出其中的內(nèi)容(innerHTML)
如果不使用事件委托,需要循環(huán)對(duì)每個(gè) li 進(jìn)行綁定,點(diǎn)擊按鈕添加新的 li 元素后也要進(jìn)行綁定,效率低下
使用事件委托,直接對(duì) ul 綁定點(diǎn)擊事件處理函數(shù),獲取事件對(duì)象、事件源對(duì)象,再對(duì)源對(duì)象進(jìn)行處理
<body> <button>btn</button> <ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li> </ul> <script> var oBtn = document.getElementsByTagName('button')[0], oList = document.getElementsByTagName('ul')[0], oLi = oList.getElementsByTagName('li'); oBtn.onclick = function () { var li = document.createElement('li'); li.innerText = oLi.length + 1; oList.appendChild(li); } oList.onclick = function (ev) { var ev = ev || window.event, tar = ev.target || ev.srcElement; // tar 即為被點(diǎn)擊的 li 元素 console.log(tar.innerHTML); // 返回在所有兄弟元素中的索引,借用數(shù)組 indexOf 方法 console.log(Array.prototype.indexOf.call(oLi, tar)); } </script> </body>
聯(lián)系客服