tips:歡迎關(guān)注我在github的博客點(diǎn)擊查看 。
遇到j(luò)s事件的問題思考,回去補(bǔ)當(dāng)初自己的總結(jié)文,受益匪淺,感謝當(dāng)初的自己??原文地址
javaScript 與 HTML 之間的交互是通過事件實(shí)現(xiàn)的。
事件流
概念:事件流描述的是從頁(yè)面中接收事件的順序。
事件冒泡
概念:即事件開始時(shí)由最具體的元素(文檔中嵌套層次最深 的那個(gè)節(jié)點(diǎn))接收,然后逐級(jí)向上傳播到較為不具體的節(jié)點(diǎn)(文檔)。

理論及運(yùn)用
- 任何可以冒泡的事件都不僅僅可以在事件目標(biāo)上進(jìn)行處理,目標(biāo)的任何祖先節(jié)點(diǎn)上也能處理。
運(yùn)用:事件委托 更詳細(xì)的介紹看這篇文章
事件委托原理:以將事件處理程序附加到更高層的地方負(fù)責(zé)多個(gè)目標(biāo)的事件處理。
大多數(shù) Web 應(yīng)用在用戶交互上大量用到事件處理程序。頁(yè)面上的事件處理程序的數(shù)量和頁(yè)面響應(yīng) 用戶交互的速度之間有個(gè)負(fù)相關(guān)。為了減輕這種懲罰,最好使用事件代理。
事件委托實(shí)例:
<ul id="list">
<li>我是孩子1</li>
<li>我是孩子2</li>
<li>我是孩子3</li>
<li>我是孩子4</li>
<li>我是孩子5</li>
</ul>
<div id = "btn">添加一個(gè)新孩子</div>
場(chǎng)景1:需要在點(diǎn)擊列表項(xiàng)的時(shí)候響應(yīng)一個(gè)事件。如果給每個(gè)列表項(xiàng)一一都綁定一個(gè)函數(shù),那對(duì)于內(nèi)存消耗是非常大的,效率上需要消耗很多性能。因此,比較好的方法就是把這個(gè)點(diǎn)擊事件綁定到他的父層,也就是 ul 上,然后在執(zhí)行事件的時(shí)候再去匹配判斷目標(biāo)元素;
場(chǎng)景2:動(dòng)態(tài)添加新的Li元素。在很多時(shí)候,我們需要通過 AJAX 或者用戶操作動(dòng)態(tài)的增加或者去除列表項(xiàng)元素,那么在每一次改變的時(shí)候都需要重新給新增的元素綁定事件,給即將刪去的元素解綁事件。如果用了事件委托就沒有這種麻煩了,因?yàn)槭录墙壎ㄔ诟笇拥?,和目?biāo)元素的增減是沒有關(guān)系的,執(zhí)行到目標(biāo)元素是在真正響應(yīng)執(zhí)行事件函數(shù)的過程中去匹配的;
代碼: 代碼運(yùn)行
//事件委托
var ul = document.getElementById('list');
ul.addEventListener('click', function (event) {
// 兼容性處理
var event = event||window.event;
var target = event.target || event.srcElement;//target表示在事件冒泡中觸發(fā)事件的源元素,在IE中是srcElement
// 判斷是否匹配目標(biāo)元素
if (target.nodeName.toLowerCase() == 'li') {
alert(target.innerHTML);
}
});
//添加li元素
document.getElementById('btn').addEventListener('click', function (event) {
var li = document.createElement('li');
li.innerHTML="我是新孩子";
ul.appendChild(li);
})
不是所有的事件都能冒泡。以下事件不冒泡:blur、focus、load、unload。
有些業(yè)務(wù)場(chǎng)景需要阻止冒泡事件的冒泡,比如在一個(gè)HTML結(jié)構(gòu)中,父級(jí)包含著子級(jí),當(dāng)事件在子級(jí)發(fā)生時(shí)(click,mouseenter,mouseleave等),由于事件冒泡就會(huì)觸發(fā)父級(jí)的同名事件。示例:
<ul id="parent">
<li id="child">son</li>
</ul>
child.addEventListener("click",function(event){
alert(1);
// event stopPropagation(); //標(biāo)準(zhǔn)瀏覽器
//或者是
//event cancelBubble = true; //IE
})
parent.addEventListener("click",function(event){
alert(1);
})
當(dāng)點(diǎn)擊li時(shí),會(huì)彈出兩個(gè)1,可以通過阻止冒泡防止這樣的行為,點(diǎn)擊li就彈出一個(gè)1。
event.stopPropagation()或event.cancelBubble = true 可以阻止事件冒泡。
- 阻止冒泡并不能阻止對(duì)象默認(rèn)行為。比如submit按鈕被點(diǎn)擊后會(huì)提交表單數(shù)據(jù),這種行為無須我們寫程序定制。
應(yīng)用
- 事件代理,事件冒泡允許多個(gè)操作被集中處理(把事件處理器添加到一個(gè)父級(jí)元素上,避免把事件處理器添加到多個(gè)子級(jí)元素上),它還可以讓你在對(duì)象層的不同級(jí)別捕獲事件
- 讓不同的對(duì)象同時(shí)捕獲同一事件,并調(diào)用自己的專屬處理程序做自己的事情,就像老板一下命令,各自員工做自己崗位上的工作去了。
事件捕獲
概念:事件捕獲的思想 是不太具體的節(jié)點(diǎn)應(yīng)該更早接收到事件,而最具體的節(jié)點(diǎn)應(yīng)該最后接收到事件。事件捕獲的用意在于在 事件到達(dá)預(yù)定目標(biāo)之前捕獲它。

DOM事件流
“DOM2級(jí)事件”規(guī)定的事件流包括三個(gè)階段::事件捕獲階段、處于目標(biāo)階段和事件冒泡階段。
首先發(fā)生的是事件捕獲,為截獲事件提供了機(jī)會(huì)。然后是實(shí)際的目標(biāo)接收到事件。最后一個(gè)階段是冒泡階 段,可以在這個(gè)階段對(duì)事件做出響應(yīng)。

在 DOM 事件流中,實(shí)際的目標(biāo)(<div>元素)在捕獲階段不會(huì)接收到事件。這意味著在捕獲階段, 事件從 document 到<html>再到<body>后就停止了。下一個(gè)階段是“處于目標(biāo)”階段,于是事件在<div> 上發(fā)生,并在事件處理(后面將會(huì)討論這個(gè)概念)中被看成冒泡階段的一部分。然后,冒泡階段發(fā)生, 事件又傳播回文檔。
事件處理程序
概念:事件就是用戶或?yàn)g覽器自身執(zhí)行的某種動(dòng)作。諸如 click、load 和 mouseover,都是事件的名字。 而響應(yīng)某個(gè)事件的函數(shù)就叫做事件處理程序(或事件偵聽器)。事件處理程序的名字以"on"開頭,onclick等。
HTML事件處理程序
概念:某個(gè)元素支持的每種事件,都可以使用一個(gè)與相應(yīng)事件處理程序同名的 HTML 特性來指定。
例如,要在按鈕被單擊時(shí)執(zhí)行一些 JavaScript,可以像下面 這樣編寫代碼:
<input type="button" value="Click Me" onclick="alert("Clicked")" />
缺點(diǎn):
首先,存在一個(gè)時(shí)差問題。因?yàn)橛脩艨赡軙?huì)在 HTML 元素一出現(xiàn)在頁(yè)面上就觸發(fā)相應(yīng)的事件,但當(dāng)時(shí)的事件處理程序有可能尚不具備執(zhí)行條件。
另一個(gè)缺點(diǎn)是,這樣擴(kuò)展事件處理程序的作用域鏈在不同瀏覽器中會(huì)導(dǎo)致不同結(jié)果。
最后一個(gè)缺點(diǎn)是 HTML 與 JavaScript 代碼緊密耦合。如果要更換事 件處理程序,就要改動(dòng)兩個(gè)地方:HTML 代碼和 JavaScript 代碼。
DOM0 級(jí)事件處理程序
簡(jiǎn)單來說就是取得一 個(gè)要操作的對(duì)象的引用,然后為它指定了某事件處理程序(如onclick)。使用 DOM0 級(jí)方法指定的事件處理程序被認(rèn)為是元素的方法,以這種方式添加的事件處理程序會(huì)在事件流的冒泡階段被處理。
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert(this.id); //"myBtn"
};
btn.onclick = null;//刪除事件處理程序
將事件處理程序設(shè)置為 null 之后,再單擊按鈕將不會(huì)有任何動(dòng)作發(fā)生。
DOM2 級(jí)事件處理程序
“DOM2 級(jí)事件”定義了兩個(gè)方法,用于處理指定和刪除事件處理程序的操作:addEventListener() 和 removeEventListener()。
onclick 事件會(huì)被覆蓋,addevenlistener 事件先后執(zhí)行。
所有 DOM 節(jié)點(diǎn)中都包含這兩個(gè)方法,并且它們都接受 3 個(gè)參數(shù):要處理的事件名、作為事件處理程序的函數(shù)和一個(gè)布爾值。最后這個(gè)布爾值參數(shù)如果是 true,表示在捕獲 階段調(diào)用事件處理程序;如果是 false,表示在冒泡階段調(diào)用事件處理程序。沒有指定的話,默認(rèn)為 false。
target.addEventListener(type, listener, useCapture);
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
上面的代碼為一個(gè)按鈕添加了 onclick 事件處理程序,而且該事件會(huì)在冒泡階段被觸發(fā)(因?yàn)樽詈笠粋€(gè)參數(shù)是 false,此時(shí)和DOM0級(jí)的onclick事件差不多)。
使用 DOM2 級(jí)方法添加事件處理程序的主要好處是可以添加多個(gè)事件處理程序。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
btn.addEventListener("click", function(){
alert("Hello world!");
}, false);
這兩個(gè)事件處理程序會(huì)按照添加它們的順序觸發(fā),因此首先會(huì)顯示元素的 ID,其次會(huì)顯示"Hello world!"消息。
MDN 對(duì)第三個(gè)參數(shù)的解釋是:Boolean,是指在DOM樹中,注冊(cè)了該listener的元素,是否會(huì)先于它下方的任何事件目標(biāo),接收到該事件。
沿著DOM樹向上冒泡的事件不會(huì)觸發(fā)被指定為為true的listener(因?yàn)榇藭r(shí)為true是比冒泡更早的事件捕獲階段)。
例子: (引自csdn的一篇文章)

<style>
.parent
{
position: relative;
background-color: coral;
border: 1px solid;
padding: 50px;
}
.child {
position: relative;
background-color: pink;
width: 100px;
height: 100px;
}
</style>
<body>
<p>該實(shí)例演示了在添加事件監(jiān)聽時(shí)冒泡與捕獲階段的不同。</p>
<div id="parent" class="parent">
<div id="child" class="child">點(diǎn)擊該方塊, 我是冒泡</div>
</div><br>
<div id="parent2" class="parent">
<div id="child2" class="child">點(diǎn)擊該方塊, 我是捕獲</div>
</div>
<script>
//事件冒泡 child的DOM事件先發(fā)生(粉),parent的DOM事件再發(fā)生(橙)。
document.getElementById("child").addEventListener("click", function(){
alert("你點(diǎn)擊了 粉色!");
}, false);
document.getElementById("parent").addEventListener("click", function(){
alert("你點(diǎn)擊了 橙色!");
}, false);
//事件捕獲 parent的DOM事件先發(fā)生(橙),parent的DOM事件再發(fā)生(粉)。
document.getElementById("child2").addEventListener("click", function(){
alert("你點(diǎn)擊了 粉色!");
}, true);
document.getElementById("parent2").addEventListener("click", function(){
alert("你點(diǎn)擊了 橙色!");
}, true);
</script>
</body>
大多數(shù)情況下,都是將事件處理程序添加到事件流的冒泡階段,這樣可以最大限度地兼容各種瀏覽器。最好只在需要在事件到達(dá)目標(biāo)之前截獲它的時(shí)候?qū)⑹录幚沓绦蛱砑拥讲东@階段。如果不是特別需 要,我們不建議在事件捕獲階段注冊(cè)事件處理程序。
事件對(duì)象
兼容 DOM 的瀏覽器會(huì)將一個(gè) event 對(duì)象傳入到事件處理程序中。無論指定事件處理程序時(shí)使用什 么方法(DOM0 級(jí)或 DOM2 級(jí)),都會(huì)傳入 event 對(duì)象。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert(event.type); //"click"
};
btn.addEventListener("click", function(event){
alert(event.type); //"click"
}, false);
觸發(fā)的事件類型不一樣,可用的屬性和方法也不一樣。常見的有event.type,event.target 等
事件類型
Web 瀏覽器中可能發(fā)生的事件有很多類型。如前所述,不同的事件類型具有不同的信息,而“DOM3 級(jí)事件”規(guī)定了以下幾類事件。
- UI(User Interface,用戶界面)事件,當(dāng)用戶與頁(yè)面上的元素交互時(shí)觸發(fā); 如resize,scroll
- 焦點(diǎn)事件,當(dāng)元素獲得或失去焦點(diǎn)時(shí)觸發(fā); 如blur (在元素失去焦點(diǎn)時(shí)觸發(fā),這個(gè)事件不會(huì)冒泡)
- 鼠標(biāo)事件,當(dāng)用戶通過鼠標(biāo)在頁(yè)面上執(zhí)行操作時(shí)觸發(fā);如click
- 滾輪事件,當(dāng)使用鼠標(biāo)滾輪(或類似設(shè)備)時(shí)觸發(fā);
- 文本事件,當(dāng)在文檔中輸入文本時(shí)觸發(fā);
- 鍵盤事件,當(dāng)用戶通過鍵盤在頁(yè)面上執(zhí)行操作時(shí)觸發(fā);
- 合成事件,當(dāng)為 IME(Input Method Editor,輸入法編輯器)輸入字符時(shí)觸發(fā);
- 變動(dòng)(mutation)事件,當(dāng)?shù)讓?DOM 結(jié)構(gòu)發(fā)生變化時(shí)觸發(fā)。
內(nèi)存和性能
事件委托
這個(gè)前面講到了 ,這邊不重復(fù)。
移除事件處理程序
每當(dāng)將事件處理程序指定給元素時(shí),運(yùn)行中的瀏覽器代碼與支持頁(yè)面交互的 JavaScript 代碼之間就 會(huì)建立一個(gè)連接。。這種連接越多,頁(yè)面執(zhí)行起來就越慢。如前所述,可以采用事件委托技術(shù),限制建立 的連接數(shù)量。另外,在不需要的時(shí)候移除事件處理程序,也是解決這個(gè)問題的一種方案。內(nèi)存中留有那 些過時(shí)不用的“空事件處理程序”(dangling event handler),也是造成 Web 應(yīng)用程序內(nèi)存與性能問題的 主要原因。
在兩種情況下,可能會(huì)造成上述問題。第一種情況就是從文檔中移除帶有事件處理程序的元素時(shí)。 這可能是通過純粹的 DOM 操作,例如使用 removeChild()和 replaceChild()方法。但更多地是發(fā) 生在使用 innerHTML 替換頁(yè)面中某一部分的時(shí)候。如果帶有事件處理程序的元素被 innerHTML 刪除 了,那么原來添加到元素中的事件處理程序極有可能無法被當(dāng)作垃圾回收。
<div id="myDiv">
<input type="button" value="Click Me" id="myBtn">
</div>
<script type="text/javascript">
var btn = document.getElementById("myBtn");
btn.onclick = function(){
//先執(zhí)行某些操作
btn.onclick = null; //移除事件處理程序
document.getElementById("myDiv").innerHTML = "處理中..."; //把按鈕替換成文字
};
</script>
這里,有一個(gè)按鈕被包含在<div>元素中。為避免雙擊,單擊這個(gè)按鈕時(shí)就將按鈕移除并替換成一 條消息;這是網(wǎng)站設(shè)計(jì)中非常流行的一種做法。但問題在于,當(dāng)按鈕被從頁(yè)面中移除時(shí),它還帶著一個(gè) 事件處理程序呢。在<div>元素上設(shè)置 innerHTML 可以把按鈕移走,但事件處理程序仍然與按鈕保持 著引用關(guān)系。有的瀏覽器(尤其是 IE)在這種情況下不會(huì)作出恰當(dāng)?shù)靥幚?,它們很有可能?huì)將對(duì)元素和 對(duì)事件處理程序的引用都保存在內(nèi)存中。如果你知道某個(gè)元素即將被移除,那么最好手工移除事件處理 程序
第二種情況,就是卸載頁(yè)面的時(shí)候。如果在頁(yè)面被卸載之前沒 有清理干凈事件處理程序,那它們就會(huì)滯留在內(nèi)存中。每次加載完頁(yè)面再卸載頁(yè)面時(shí)(可能是在兩個(gè)頁(yè) 面間來回切換,也可以是單擊了“刷新”按鈕),內(nèi)存中滯留的對(duì)象數(shù)目就會(huì)增加,因?yàn)槭录幚沓绦?占用的內(nèi)存并沒有被釋放。一般來說,最好的做法是在頁(yè)面卸載之前,先通過 onunload 事件處理程序移除所有事件處理程序。 在此,事件委托技術(shù)再次表現(xiàn)出它的優(yōu)勢(shì)——需要跟蹤的事件處理程序越少,移除它們就越容易。對(duì)這 種類似撤銷的操作,我們可以把它想象成:只要是通過 onload 事件處理程序添加的東西,最后都要通 過 onunload 事件處理程序?qū)⑺鼈円瞥?/p>
模擬事件
概念:使用 JavaScript 在任意時(shí)刻來觸發(fā)特定的事件,此時(shí)的事件就如同瀏覽器創(chuàng)建的事件一樣。
模擬鼠標(biāo)事件
思路: 1.創(chuàng)建事件對(duì)象 2.分發(fā)事件
//創(chuàng)建事件對(duì)象
var event = new MouseEvent('click', {
cancelable: true,
bubble: true,
view: document.defaultView
});
//分發(fā)事件
document.getElementById("myBtn").dispatchEvent(event);
ps:紅包書介紹的創(chuàng)建事件對(duì)象的方法有點(diǎn)過時(shí)了,現(xiàn)被MDN不建議使用
var event = document.createEvent("MouseEvents"); //創(chuàng)建事件對(duì)象
event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null); //初始化事件對(duì)象

實(shí)例運(yùn)用 代碼運(yùn)行
這邊有兩個(gè)按鈕,按鈕1綁定一個(gè)鼠標(biāo)點(diǎn)擊事件1,alert出一句話;在按鈕2綁定一個(gè)鼠標(biāo)點(diǎn)擊事件2,鼠標(biāo)點(diǎn)擊事件2將模擬用戶去點(diǎn)擊按鈕1。所以點(diǎn)按鈕2,觸發(fā)一個(gè)模擬鼠標(biāo)事件去點(diǎn)擊按鈕1。
<body>
<button id="btn1">我是按鈕1</button>
<button id="btn2">我是按鈕2,點(diǎn)我一下 按鈕1也會(huì)被點(diǎn)擊哦</button>
<script type="text/javascript">
var btn=document.getElementById("#btn1");
// 按鈕1的點(diǎn)擊事件
btn.addEventListener('click',function(e){
console.log("我是按鈕1,我被點(diǎn)擊啦") // 點(diǎn)擊按鈕2,按鈕1也會(huì)被點(diǎn)擊(模擬事件);只點(diǎn)擊按鈕1(正常事件)
})
// 按鈕2的點(diǎn)擊事件
document.querySelector("#btn2").addEventListener('click',function(e){
//模擬鼠標(biāo)事件步奏1 創(chuàng)建事件對(duì)象
var event = new MouseEvent('click', {
cancelable: true,
bubble: true,
view: document.defaultView
});
//模擬鼠標(biāo)事件步奏2 觸發(fā)事件
btn.dispatchEvent(event);
})
</script>
</body>
模擬鍵盤事件
這邊舉一反三,不做解釋。
模擬其他事件
自定義 DOM 事件
實(shí)例運(yùn)用 代碼運(yùn)行
<body>
<button id="btn1">我是按鈕1,我沒鼠標(biāo)事件,點(diǎn)我不會(huì)輸出</button>
<button id="btn2">我是按鈕2,點(diǎn)我一下會(huì)執(zhí)行綁定在按鈕1的自定義事件,會(huì)有輸出</button>
<script type="text/javascript">
var btn= document.querySelector("#btn1");
// 按鈕1的點(diǎn)擊事件
btn.addEventListener('test',function(e){
console.log("我是按鈕1,我被點(diǎn)擊啦") // 點(diǎn)擊按鈕2,有輸出(自定義事件);只點(diǎn)擊按鈕1,沒輸出(自定義事件,非模擬鼠標(biāo)事件)
})
// 按鈕2的點(diǎn)擊事件
document.querySelector("#btn2").addEventListener('click',function(e){
//自定義事件步奏1 創(chuàng)建事件對(duì)象
var event = new Event('test', {
cancelable: true,
bubble: true,
view: document.defaultView
});
//自定義鼠標(biāo)事件步奏2 觸發(fā)事件
btn.dispatchEvent(event);
})
</script>
</body>
紅寶書的方法有點(diǎn)過時(shí),新實(shí)現(xiàn)方法可看這篇文章