前言
前段時(shí)間在周會(huì)上被迫分享了一些 JavaScript 基礎(chǔ)知識(shí),后來(lái)聽小伙伴反饋,組織性和邏輯性太亂,今天自己特意翻了下書本(JavaScript高級(jí)程序設(shè)計(jì)),以下是閱讀后的整理,以便后續(xù)溫故知新。
嗯,今天的主角是事件
事件
定義
以我的理解,事件就是指一系列的動(dòng)作,像我們經(jīng)常接觸的click、dbclick以及鍵盤的keydown、keyup或者還有一些文檔(DOM)的加載、圖片的加載,焦點(diǎn)事件等等。其實(shí)事件的主要目的就是為了JavaScript和HTML更好的交互。
課本上的定義是:
事件,就是文檔或?yàn)g覽器窗口中發(fā)生的一些特定的交互瞬間
事件流
事件流是事件中一個(gè)重要的概念,由于歷史(IE與Netscape巴拉巴拉~)的原因,出現(xiàn)了兩種完全相反的事件流概念。
首先看下事件流的定義:
事件流是描述頁(yè)面接收事件的順序
顧名思義,事件流就是指事件的流向嘛,這個(gè)很好理解。前面說兩位爸爸提出了兩個(gè)截然相反事件流概念,是啥呢?我想大家都應(yīng)該有聽過 事件捕獲和事件冒泡
事件捕獲
直接上圖:

舉個(gè)例子
<html>
<body>
<div>今晚該點(diǎn)我了,小哥!</div>
</body>
</html>
單擊div元素,click將會(huì)以以下順序發(fā)生:
- document
- html
- body
- div
注意:
1.IE9一下不支持事件捕獲;
2.DOM2級(jí)規(guī)范要求事件從document對(duì)象開始傳播,但瀏覽器多數(shù)都是從window對(duì)象開始傳播
事件冒泡
首先,說個(gè)重點(diǎn)因?yàn)闉g覽器兼容性問題,早期(IE9之前)是不支持事件捕獲的,所以建議使用時(shí)間冒泡處理事件
上圖

同樣的代碼(時(shí)間捕獲的代碼)
事件的順序剛好相反:
- div
- body
- html
- document
DOM事件流
DOM2級(jí)事件 規(guī)定事件流包括3個(gè)階段
- 事件捕獲階段
- 目標(biāo)階段
- 時(shí)間冒泡階段
在DOM事件流中,實(shí)際的目標(biāo)(div)在捕獲階段是不會(huì)接收到事件的,這意味著事件從documen->html->body就停止了。在事件處理中,將"處于目標(biāo)階段"看做是冒泡階段的一部分。
但是,多數(shù)支持DOM2事件流的現(xiàn)代瀏覽器都實(shí)現(xiàn)了在捕獲階段觸發(fā)事件對(duì)象上的事件,so~
以上是書本原話,大家自行體會(huì)。
注意,IE9以下是不支持DOM2級(jí)事件流的
事件處理程序
用來(lái)響應(yīng)某個(gè)事件的函數(shù)叫做事件處理程序(或事件偵聽器)
為事件指定處理程序的方式有以下幾種:
- HTML事件處理程序
- DOM0級(jí)時(shí)間處理程序
- DOM2級(jí)事件處理程序
- IE事件處理程序
HTML 事件處理程序
看代碼
- 方法一
<input type='button' value='Click me' onclick='alert(123)' />
- 方法二
<script>
function showMessage(){
console.log(123);
}
</script>
<input type='button' value='Click me' onclick='showMessage()' />
這兩種注冊(cè)事件的方式,大家應(yīng)該都不陌生,需要一提的是在方法一中,代碼部分(alert(123))是作為JavaScript代碼來(lái)處理的且是嵌套在html代碼中的,如果代碼中包含未經(jīng)過轉(zhuǎn)義的HTMl語(yǔ)法字符是有問題的,所以請(qǐng)轉(zhuǎn)義OR用第二種方法。
代碼執(zhí)行時(shí)的作用域問題
- 在代碼執(zhí)行時(shí)以上兩種方法都有權(quán)訪問全局作用域中的任何代碼
- 當(dāng)前的
this指向目標(biāo)元素
作用域擴(kuò)展
看實(shí)例代碼:JSFiddeler代碼示例
代碼截圖

- 在函數(shù)內(nèi)部,你可以像訪問局部變量一樣訪問document及該元素本身的成員
- 如果當(dāng)前元素是一個(gè)表單的輸入元素,則作用域中還會(huì)包含訪問表單元素(父元素)的入口,這樣可以更快的訪問表單字段。
HTML事件處理程序的缺點(diǎn)
- 時(shí)差問題,當(dāng)事件觸發(fā)時(shí),事件處理程序可能尚不具備執(zhí)行條件
舉個(gè)例子,前面的showMessage如果在按鈕的下方,用戶點(diǎn)擊按鈕時(shí),showMessage還未加載完畢就會(huì)出現(xiàn)錯(cuò)誤 - 作用域鏈可能因?yàn)闉g覽器的的不同產(chǎn)生差異
- HTML代碼和JavaScript代碼緊密耦合
DOM0 級(jí)時(shí)間處理程序
先說優(yōu)點(diǎn)
- 簡(jiǎn)單
- 兼容性非常好,具有跨瀏覽器的優(yōu)勢(shì)
看看如何注冊(cè)
var btn = document.getElementById('mybtn');
btn.onclick = function(){
console.log('clicked');
}
嗯,簡(jiǎn)單不需要多說什么。
幾點(diǎn)需要注意的
- 處理程序中this指向當(dāng)前元素
- 該種方式添加的事件處理程序都是在事件流的冒泡階段被處理的
- 當(dāng)
btn.onclick=null時(shí),可以去除事件綁定(有好處,日后展開)
DOM2 級(jí)處理程序
DOM2級(jí)事件定義了兩個(gè)方法,用來(lái)處理和刪除事件處理程序addEventListener()、removeEventListener();所有實(shí)現(xiàn)DOM2級(jí)規(guī)范的dom節(jié)點(diǎn)都包含這兩個(gè)方法;這兩個(gè)函數(shù)有三個(gè)參數(shù),依次順序分別是:要處理的事件名稱(click,load等)、處理函數(shù)、以及一個(gè)布爾值。最后這個(gè)布爾值如果是true表示在捕獲階段調(diào)用時(shí)間處理程序,否則在冒泡階段調(diào)用。
看代碼
var btn = document.getElementById('mybtn');
btn.addEventListener('click',function(){
console.log(1111);
},true);
如果需要將事件處理程序移除,必須使用有簽名的處理函數(shù),上述代碼的匿名函數(shù)則無(wú)法移除`
btn.removeEventListener("click",handler,true)
優(yōu)點(diǎn)
- 可以添加多個(gè)處理程序
- 解耦
福利:查看代碼 ,可以稍稍理解下這里的捕獲階段調(diào)用事件處理程序和冒泡階段調(diào)用是啥意思
IE處理程序
說實(shí)話,我個(gè)人是不太想說這個(gè)的,因?yàn)樗鼤?huì)讓人混亂,我建議還是先了解標(biāo)準(zhǔn)之后再來(lái)閱讀這個(gè)比較好。但為了知識(shí)的連貫性,我還是大概說一下下。
IE早期的版本(我試了ie11好像已經(jīng)去除了,估計(jì)還是ie9之前,具體沒有找到資料)中實(shí)現(xiàn)了類似DOM2中的兩個(gè)方法attachEvent、detachEvent。
以下列舉這兩個(gè)方法的不同
- 因?yàn)镮E8及更早的版本只支持時(shí)間冒泡,so 通過該方法注冊(cè)的事件處理程序都會(huì)被添加到冒泡階段
- 看下代碼
多了個(gè) onbtn.attachEvent('onclick',handler) - this指向window
- 注冊(cè)了的多個(gè)處理程序的執(zhí)行順序和DOM2順序相反,DOM2是先注冊(cè)的先執(zhí)行
跨瀏覽器的事件處理程序
var EventUtil = {
addHandler : function(element, type, handler){
if(element.addEventListener){
element.addEventListener(type,handler,false);
}else if(element.attachEvent){
element.attachEvent('on' + type, handler)
}else{
element['on' + type] = handler;
}
},
removeHandler : function(element,type,handler){
if(element.removeEventListener){
element.removeEventListener(type,handler,false);
}else if(element.detachEvent){
element.detachEvent('on'+type,handler);
}else{
element['on' + type] = null;
}
}
}