JavaScript與HTML交互系列--事件流(冒泡、捕獲、應用)和事件處理程序

事件流

當瀏覽器發展到第四代時(IE4及Netscape Communicator4),瀏覽器開發團隊遇到了一個頗有意思的問題:頁面的哪一部分會擁有某個特定的事件?要明白這個問題問的是什麼,能夠想象畫在一張紙上的一組同心圓。若是你把手指放在圓心上,那麼你的手指指向的不是一個圓,而是紙上的全部圓。兩家公司的瀏覽器開發團隊在看待瀏覽器事件方面仍是一致的。若是你單擊了某個按鈕,他們都認爲單擊事件不單單發生在按鈕上。換句話說,在單擊按鈕的同時,你也單擊了按鈕的容器元素,甚至也單擊了整個頁面。javascript

事件流 描述的是從頁面中接收事件的順序。但有意思的是,IE和Netscape開發團隊竟然提出了差很少是徹底相反的事件流的概念。IE的事件流是事件冒泡流,而Netscape Communicator的事件流是事件捕獲流。html

思考:點擊頁面元素,什麼樣的元素能感應到這樣一個事件?java

點擊頁面元素的同時,也點擊了元素的容器元素甚至整個頁面。瀏覽器

事件冒泡

IE的事件流叫作事件冒泡(event bubbling),即事件開始時由最具體的元素(文檔中嵌套層次最深的那個節點)接收,而後逐級向上傳播到較爲不具體的節點(文檔)。函數

事件捕獲

Netscape Communicator 團隊提出的另外一種事件流叫作事件捕獲(event capturing)。其思想是不太具體的節點應該更早接收到事件,而最具體的節點應該最後接收到事件。事件捕獲的用意在於事件到達預訂目標以前捕獲它。ui

DOM 事件流

「DOM2級事件」規定的事件流包括三個階段:事件捕獲階段、處於目標階段和事件冒泡階段。首先發生的是事件捕獲,爲截獲事件提供了機會。而後是實際的目標接收到事件。最後一個階段是冒泡階段,(通常狀況下,程序)能夠在冒泡階段對事件做出響應。this

事實上,DOM事件流在捕獲階段和冒泡階段都可觸發事件處理程序。捕獲階段先收到事件流spa

對上面元素添加事件綁定程序。

var outBox = document.getElementById("outBox");
var middleBox = document.getElementById("middleBox");
var innerBox = document.getElementById("innerBox");

outBox.addEventListener('click', function() {
    console.log('outBox捕獲');
}, true);
middleBox.addEventListener('click', function() {
    console.log('middleBox捕獲');
}, true);
innerBox.addEventListener('click', function() {
    console.log('innerBox捕獲');
}, true);

outBox.addEventListener('click', function() {
    console.log('outBox冒泡');
}, false);
middleBox.addEventListener('click', function() {
    console.log('middleBox冒泡');
}, false);
innerBox.addEventListener('click', function() {
    console.log('innerBox冒泡');
}, false);

// 輸出順序
// 1 outBox捕獲
// 2 middleBox捕獲
// 3 innerBox捕獲
// 4 innerBox冒泡
// 5 middleBox冒泡
// 6 outBox冒泡
複製代碼

IE九、Opera、Firefox、Chrome和Safari(也就是說當下主流瀏覽器)都支持DOM事件流;IE8及更早版本不支持DOM事件流。代理

思考一下:事件冒泡和事件捕獲在平常開發中有哪些應用場景?code

咱們下面先看一下事件處理程序。

事件處理程序

1. HTML 事件處理程序

爲元素添加(事件)屬性,值爲事件處理程序。

注:事件屬性所有爲小寫。

<div class="outBox" onclick="alert('outBox')">
    <div class="innerBox" onclick="showInnerBox()"></div>
</div>

<script> function showInnerBox() { alert("innerBox"); } </script>
複製代碼

特色:傳統方式,簡單,跨瀏覽器

2. DOM0 級事件處理程序

使用JavaScript指定事件處理程序,首先必須取得一個要操做的對象的引用。

<div class="outBox" id="outBox">
    <div class="innerBox" id="innerBox"></div>
</div>

<script> var outBoxElement = document.getElementById('outBox'); outBoxElement.onclick = function() { alert(this.id) } // 刪除事件處理程序 outBoxElement.onclick = null </script>
複製代碼

特色:可經過this訪問元素的任何屬性和方法(HTML事件處理程序也能夠),能夠刪除事件處理程序

3. DOM2 級事件處理程序

「DOM2級事件」定義了兩個方法,用於處理指定和刪除事件處理程序的操做:addEventListener()removeEventLintener()。全部DOM結點中都包含這兩個方法,而且他們都接受3個參數:要處理的事件名、做爲事件處理程序的函數和一個布爾值。最後這個布爾值若是是true,表示在捕獲階段調用事件處理程序;若是是false,表示在冒泡階段調用事件處理程序。

// 添加事件綁定
outBoxElement.addEventListener('click', showOutBox, false);
// 移除事件綁定
outBoxElement.removeEventListener('click', showOutBox);
// 下面移除事件綁定方式無效,沒法移除綁定的匿名函數
outBoxElement.removeEventListener('click', function() {
    alert(this.id);
});
複製代碼

4. IE 事件處理程序

IE實現了與DOM中相似的兩個方法:attachEvent() 和 detachEvent()。這兩個方法接受相同的個參數:事件名稱和事件處理函數。因爲IE8及更早版本只支持事件冒泡,因此經過attachEvent()添加的事件處理程序都會被添加到冒泡階段。

var btn = document.getElementById('MyBtn');
btn.attachEvent('onclick', function() {
    console.log('clicked');
});

var handler = function() {
    console.log('clicked');
}
// 第二個參數必須是具名函數,不能是匿名函數
btn.detachEvent('onclick', handler);
複製代碼

注:attachEvent() 的第一個參數是'onclick',而非DOM的addEventListener()方法中的'click'。

在IE中使用attachEvent()與使用DOM0級方法的主要區別在於時間處理程序的做用域。在使用DOM0級方法的狀況下,事件處理程序會在其所屬元素的做用域內運行;在使用attachEvent()方法的狀況下事件處理程序會在全局做用域中運行,所以this等於window

btn.attachEvent('onclick', function() {
    console.log(this === window); // true
})
複製代碼

此外,attachEvent() 與addEventListener() 相似都可覺得一個元素添加多個事件處理程序。

// 爲同一個元素添加多個事件處理程序
btn.addEventListener('click', function() {
    console.log('clicked');
}, false);
btn.addEventListener('click', function() {
    console.log('hello world');
}, false);

btn.attachEvent('onclick', function() {
    console.log('clicked');
});
btn.attachEvent('onclick', function() {
    console.log('hello world');
});
複製代碼

5. 跨瀏覽器事件處理程序

跨瀏覽器事件處理程序即一套能夠同時運行在IE以及非IE瀏覽器的程序,重點是瀏覽器能力檢測也就是瀏覽器兼容性檢查。

var EventUtil = {
    // 添加事件綁定(元素,事件類型,事件處理函數)
    addHandler: function(element, type, handler) {
        if(element.addEventListenter) {
            // DOM2級綁定事件
            element.addEventListenter(type, handler, false);
        }else if(element.attachEvent) {
            // IE綁定事件
            element.attachEvent('on' + type, handler)
        }else {
            // DOM0級綁定事件,兼容IE8及更早版本
            element['on' + type] = handler;
        }
    },
    // 刪除事件事件綁定(元素,事件類型,事件處理函數)
    removeHandler: function() {
        if(element.addEventListenter) {
            // DOM2級刪除綁定事件
            element.removeEventListenter(type, handler, false);
        }else if(element.attachEvent) {
            // IE刪除綁定事件
            element.detachEvent('on' + type, handler)
        }else {
            // DOM0級刪除綁定事件,兼容IE8及更早版本
            element['on' + type] = null;
        }
    }
}

var btn = document.getElementById('MyBtn');
var handler = function() {
    console.log('clicked');
}
// 添加綁定事件
EventUtil.addHandler(btn, 'click', handler);
複製代碼

事件冒泡應用-事件委託/代理

對「事件處理程序過多」問題的解決方案就是事件委託。事件委託利用了事件冒泡,只指定一個事件處理程序,就能夠管理一類型的全部事件。例如,click事件會一直冒泡到document層次。也就是說咱們能夠爲整過頁面(通常是某幾個元素的父元素)指定一個onclick事件處理程序,而沒必要給每一個可單擊的元素分別添加事件處理程序。

<ul id="items">
    <li id="item1">item1</li>
    <li id="item2">item2</li>
    <li id="item3">item3</li>
</ul>
複製代碼
var itemList = document.getElementById('items');
EventUtil.addHandler(itemList, 'click', function(e) {
    var id = e.target.id;
    switch(id) {
        case "item1":
            alert('item1 is clicked!');
            break;
        case "item2":
            alert('item2 is clicked!');
            break;
        case "item3":
            alert('item3 is clicked!');
            break;
        case "items":
            alert('items is clicked!');
            break;
    }
});
複製代碼

總結

這是一篇關於JavaScript中事件相關介紹的基礎文章,方便咱們快速溫習。

參考文章

javaScript事件(一)事件流

相關文章
相關標籤/搜索