1. 程式人生 > >瀏覽器事件流

瀏覽器事件流

事件流描述的是從頁面中接受事件的順序。但有意思的是,IE和Netscape開發團隊居然提出了兩個截然相反的事件流概念。

  1、IE的事件流是 事件冒泡流,

   2、標準的瀏覽器事件流是 事件捕獲流。

     不過addEventLister給出了第三個引數同時支援冒泡與捕獲,下文將介紹

 

事件冒泡

ie 的事件流叫事件冒泡,也就是說事件的傳播為:從事件開始的具體元素,一級級往上傳播到較為不具體的節點。案例如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>事件冒泡</title>
</head>
<body>
    <div>
        <p>點我</p>
    </div>
</body>
</html>

當我們點選P元素時,事件是這樣傳播的:

(1) p

(2) div

(3) body

(4) html

(5) document

現代瀏覽器都支援事件冒泡,IE9、Firefox、Chrome和Safari則將事件一直冒泡到window物件。

 

事件捕獲

   Netscape團隊提出的另一種事件流叫做事件捕獲。它的原理剛好和事件冒泡相反,它的用意在於在事件到達預定目標之前捕獲它,而最具體的節點應該是最後才接收到事件的。

比如還是上面的案例,當點選P元素時,事件的傳播方向就變成了這樣:

(1) document

(2) html

(3) body

(4) div

(5) p

 

IE9、Firefox、Chrome和Safari目前也支援這種事件流模型,但是有些老版本的瀏覽器不支援,所以很少人使用事件捕獲,而是用事件冒泡的多一點。

 

DOM事件流

   "DOM2級事件"規定的事件流包括三個階段:事件捕獲階段、處於目標階段、事件冒泡階段。

  首先發生的事件捕獲,為截獲事件提供機會。然後是實際的目標接受事件。最後一個階段是時間冒泡階段,可以在這個階段對事件做出響應。以前面的例子,則會按下圖順序觸發事件。

  技術分享圖1-1(圖片來源於課本,所以沒有上面案例的p標籤)

  在DOM事件流中,事件的目標在捕獲階段不會接受到事件。這意味著在捕獲階段,事件從document到p後就定停止了。

  下一個階段是處於目標階段,於是事件在p上發生,並在事件處理中被看成冒泡階段的一部分。然後,冒泡階段發生,事件又傳播回document。  

     多數支援DOM事件流的瀏覽器都實現了一種特定的行為;即使“DOM2級事件”規範明確要求捕獲階段不會涉及事件目標,但IE9、Safari、Chrome、Firefox和Opera9.5及更高版本都會在捕獲階段觸發事件物件上的事件。結果,就是有兩個機會在目標物件上操作事件

 

事件處理程式

事件就是使用者或者瀏覽器自身執行某種動作,比如click、load、mouseover。
而響應某個事件的函式就叫做事件處理程式(事件監聽器),事件處理程式的名字以on開頭,click=>onclick、load=>onload

DOM2提供了兩個方法來讓我們處理和刪除事件處理程式的操作:addEventListener()和removeEventListener
btn.addEventListener(eventType, function () {
}, false);

該方法應用至dom節點
第一個引數為事件名
第二個為事件處理程式
第三個為布林值,true為事件捕獲階段呼叫事件處理程式,false為事件冒泡階段呼叫事件處理程式

 

使用例子如下:

 var btn = document.getElementById(‘btn‘);
 btn.addEventListener(‘click‘, function () {
             alert(‘事件捕獲‘);
        }, true);
 btn.addEventListener(‘click‘, function () {
            alert(‘事件冒泡‘);
        }, false);

依次彈出“事件捕獲”和“事件冒泡”,在這裡的第一反應就是,他們有先後順序嗎?

於是我反過來寫:

  var btn = document.getElementById(‘btn‘);
  btn.addEventListener(‘click‘, function () {
            alert(‘事件冒泡‘);
        }, false);
  btn.addEventListener(‘click‘, function () {
             alert(‘事件捕獲‘);
        }, true);

依次彈出的是:“事件冒泡”和“事件捕獲”;到底有沒有先後順序呢?繼續往下走

這次是這樣的寫法:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>事件冒泡</title>
</head>
<body>
    <div>
        <p id="parEle">我是父元素    <span id="sonEle">我是子元素</span></p>
    </div>
</body>
</html>
<script type="text/javascript">
var sonEle = document.getElementById(‘sonEle‘);
var parEle = document.getElementById(‘parEle‘);

parEle.addEventListener(‘click‘, function () {
    alert(‘父級 冒泡‘);
}, false);
parEle.addEventListener(‘click‘, function () {
    alert(‘父級 捕獲‘);
}, true);

sonEle.addEventListener(‘click‘, function () {
    alert(‘子級冒泡‘);
}, false);
sonEle.addEventListener(‘click‘, function () {
    alert(‘子級捕獲‘);
}, true);

</script>

當點選“我是子元素” 時,彈出的順序是:“父級捕獲”--》“子級冒泡”--》“子集捕獲”--》“父集冒泡”;

這裡可以說明,當點選子元素時,父級的執行順序是先捕獲,後冒泡的。

  綜合前面的程式碼,我們可以得出一個小小的結論:

  當容器元素及巢狀元素,即在捕獲階段又在冒泡階段呼叫事件處理程式時:事件按DOM事件流的順序執行事件處理程式,也就是如圖1-1所示

  且當事件處於目標階段時,事件呼叫順序決定於繫結事件的書寫順序,按上面的例子為,先呼叫冒泡階段的事件處理程式,再呼叫捕獲階段的事件處理程式。依次alert出“子集冒泡”,“子集捕獲”。

 

刪除一個事件處理程式,用removeEventListener。例:

var btn=document.getElementById(‘myBtn‘);
var myFn=function(){
    alert(this.id);
}
btn.addEventListener(‘click‘,myFn,false);
btn.removeEventListener(‘click‘,myFn,false);

 

注意點:為了最大限度的相容,大多是情況下都是將事件處理程式新增到事件冒泡階段。不是特別需要,不建議在事件捕獲階段註冊事件處理程式

 

相容ie瀏覽器寫法:

 

ie新增和刪除事件處理程式的寫法有點小區別,所以用以下程式碼可以做個相容性處理:

var EventUtil = {
    addHandler: function (el, type, handler) {
        if (el.addEventListener) {
            el.addEventListener(type, handler, false);
        } else {
            el.attachEvent(‘on‘ + type, handler);
        }
    },
    removeHandler: function (el, type, handler) {
        if (el.removeEventListener) {
            el.removeEventListerner(type, handler, false);
        } else {
            el.detachEvent(‘on‘ + type, handler);
        }
    }
};

用法和前面的類似:

EventUtil.addHandler(‘btn‘,‘click‘,handler);

 

 

事件物件

觸發dom上的某個事件時,會產生一個事件物件,裡面包含著所有和事件有關的資訊。

比較常用的有以下幾個:

currentTarget   事件處理程式當前正在處理事件的那個元素(始終等於this)

preventDefault  取消事件預設行為,比如連結的跳轉

stopPropagation 取消事件冒泡

target  事件的目標

  

相容ie的事件物件:

var EventUtil = {
    addHandler: function (el, type, handler) {
        if (el.addEventListener) {
            el.addEventListener(type, handler, false);
        } else if (el.attachEvent) {
            el.attachEvent(‘on‘ + type, handler);
        } else {
            el[‘on‘ + type] = handler;
        }
    },
    removeHandler: function (el, type, handler) {
        if (el.removeEventListener) {
            el.removeEventListerner(type, handler, false);
        } else if (el.detachEvent) {
            el.detachEvent(‘on‘ + type, handler);
        } else {
            el[‘on‘ + type] = null;
        }
    },
    getEvent: function (e) {
        return e ? e : window.event;
    },
    getTarget: function (e) {
        return e.target ? e.target : e.srcElement;
    },
    preventDefault: function (e) {
        if (e.preventDefault) {
            e.preventDefault();
        } else {
            e.returnValue = false;
        }
    },
    stopPropagation: function (e) {
        if (e.stopPropagation) {
            e.stopPropagation();
        } else {
            e.cancelBubble = true;
        }
    }
};

 

 

其他

 

html5事件之 beforeunload

在頁面解除安裝前觸發,就像編輯部落格園文章未儲存是彈出的提示框一樣。

 

html5事件之 DOMContentLoaded事件

支援頁面下載前新增事件,而不需要等待圖片,css檔案,或者其他檔案載入完畢才執行。可以讓使用者能夠儘早的與使用者進行互動。

EventUtil.addHandler(document,‘DOMContentLoaded‘,function(){
    alert(‘我可以先執行,哈哈‘)
})

 

事件委託:

   每個函式都是物件,都會佔用記憶體,記憶體中的物件越多,效能就越差。對事件處理程式過多問題的解決方案就是事件委託。

   事件委託利用事件冒泡,只指定一個事件處理程式即可,就可以管理某一個型別的所有事件。例如:

有三個li,都需要一個click事件,此時不需要給每個li都繫結click事件,主要給他的父級 ul增加一個繫結事件即可。這樣點選li,利用冒泡,直接觸發ul的click,只要判斷是哪個li的id

點選即可。而不需要三個li都繫結click事件。

<ul id="myLinks">
    <li id="myLi1">text1</li>
    <li id="myLi2">text2</li>
    <li id="myLi3">text3</li>
</ul>

 

事件委託原理:事件冒泡機制。
優點:1.可以大量節省記憶體佔用,減少事件註冊。比如ul上代理所有li的click事件就很不錯。
2.可以實現當新增子物件時,無需再對其進行事件繫結,對於動態內容部分尤為合適
缺點:事件代理的常用應用應該僅限於上述需求,如果把所有事件都用事件代理,可能會出現事件誤判。即本不該被觸發的事件被繫結上了事件。

二、事件委託如何工作?

我們現在的疑問是:ul元素如何知道li元素點選了呢?

很簡單,由於所有li元素都是ul元素的子節點,故他們的事件會冒泡,無論點選哪個li元素,實際上都相當於點選了ul元素。

現在產生了另一個問題:ul元素如何知道是在哪個li元素上點選的呢?

我們很容易想到,在ul的事件處理程式中檢測事件物件的target屬性,就可以得到真正點選的目標元素。

三、事件委託的優點

首先,我們看到新增的事件處理程式減少,可以只有一個事件處理程式。由於每個函式都是物件,物件會佔用記憶體,記憶體的佔用關係到效能。因此第一個優點是:

減少了記憶體佔用,效能更好;

在訪問DOM方面,也使得DOM訪問次數減少。試想一下,如果要為許多的DOM元素繫結事件,自然需要多次訪問DOM元素,設定事件處理程式所需時間更長,整個頁面就緒需要的時間越多。因此第二個優點是:

設定事件處理程式所需時間更少,加快了整個頁面的互動就緒時間。

假使我們將事件處理程式繫結到document物件上,只要可單擊的元素呈現在頁面上,就可以立即具備適當的功能。即還會有一個額外的優點:

document很快就可以訪問,而且可以在頁面生命週期的任何時點新增事件處理程式,而不用等待其他事件完成如DOMContentLoaded、load事件。