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