JS專題之事件模型

本文共 1960 字,讀完只需 8 分鐘javascript

事件

用戶與網頁交互是經過事件實現的,事件剛開始是做爲分擔服務器負載的一個手段,起初沒有統一的規範,直到 DOM2 級,網景和 IE 纔開始有各自的 API 規範。html

對於事件的觸發機制,兩個公司都認爲頁面的觸發機制並不僅是點擊了某個元素,就只觸發當前目標元素的事件。java

比方說:頁面有多個同心圓,當點擊最裏面的圓時,你其實也點擊了包含這個圓外面的那些圓。 兩個公司對這點的認同是一致的,可是事件流的傳播順序上採用了不一樣的兩種方案來實現,即事件冒泡和事件捕獲。瀏覽器

1、事件冒泡

IE 瀏覽器從老版本開始就一直支持事件冒泡機制,所謂事件冒泡,指事件流開始是從較爲具體的元素接收,一直傳播上不具體的元素上。服務器

就是從目標元素傳播到父級元素。app

<body>
    <div id="parent">
        <div id="child"></div>
    </div>
    <script> function childEventHandler(event) { console.log(this); console.log("child 被點擊了"); } function parentEventHandler(event) { console.log(this); console.log("parent 被點擊了"); } function bodyEventHandler(event) { console.log(this); console.log("body 被點擊了"); } function htmlEventHandler(event) { console.log(this); console.log("html 被點擊了"); } function documentEventHandler(event) { console.log(this); console.log("document 被點擊了"); } function windowEventHandler(event) { console.log(this); console.log("window 被點擊了"); } var bodyEl = document.getElementsByTagName("body")[0]; var htmlEl = document.getElementsByTagName("html")[0]; var win = window; var parentEl = document.getElementById("parent"); var childEl = document.getElementById("child"); childEl.onclick = childEventHandler; parentEl.onclick = parentEventHandler; bodyEl.onclick = bodyEventHandler; htmlEl.onclick = htmlEventHandler; document.onclick = documentEventHandler; win.onclick = windowEventHandler; </script>
</body>
複製代碼

以下圖所示,若是點擊了 id 爲 child 的元素後,事件流會從 child 一直傳播到 window 對象。dom

全部現代瀏覽器都支持事件冒泡。函數

2、事件捕獲

由網景公司主導的事件捕獲則恰好和事件冒泡相反,事件流開始從不具體的元素觸發,而後傳播到具體的元素上。簡而言之就是從父級元素傳播到目標元素。性能

因爲事件捕獲是從 IE 9開始支持,不兼容老版本瀏覽器,因此使用的人比較少。ui

3、事件流

DOM 規定事件包括三個階段,事件捕獲,處於目標階段、事件冒泡。

從 IE 9 開始的瀏覽器規定,事件流的順序先是事件捕獲,會截獲到事件,而後是處於目標階段,實際的目標接收到事件,最後是事件冒泡,能夠在這個階段對事件進行響應。

以以前的 child 元素爲例,直到 child 元素接收到事件前(從 window 到 parent),都是事件捕獲階段。到了 child 元素,此時對事件進行處理,隨後冒泡到 window 對象上,冒泡階段也是能夠對事件進行處理的。 基於事件冒泡能對事件進行處理的特色,隨後將講到與其有關的事件委託機制。

4、事件綁定

HTML 與 事件的綁定有三種形式:

1. 
<div id="child" onclick="console.log('hello');"></div>

2. 

var childEl = document.getElementById("child");
childEl.onclick = function() {
    console.log('hello');
}

3. 
var childEl = document.getElementById("child");
childEl.addEventListener('click', function() {
    console.log('hello');
}, false);
複製代碼

JavaScript 是單線程的語言,在遇到元素有事件觸發時,會在事件隊列中尋找有沒有與這個事件綁定的函數,若是沒有則什麼都不作,若是有,則將該函數放到事件隊列的前面,等待主線程事件執行完畢後執行。

上述代碼第一種綁定,將事件寫在 html 中,表現和行爲沒有解耦,是不建議這樣寫代碼的。

第二種綁定,將事件綁定在元素對象上,這種寫法主要是容易發生事件的覆蓋。

第三種綁定,首先,第三個參數爲布爾值,默認爲 false, 表示在事件冒泡階段調用事件處理程序,若是爲 true, 則表示在事件捕獲階段調用事件處理函數。

當咱們想處理完一次事件後,將不想在處理該元素的事件綁定時,應該將元素的事件綁定置爲空,若是容易發生內存泄漏。

第一種寫法:
childEl.onclick = null;


第三種寫法:
function eventHandler() {
    console.log('hello');
}

childEl.addEventListener('click', eventHandler, false);

childEl.removeEventListener('click', eventHandler, false);
複製代碼

5、事件委託(事件代理)

事件委託是利用事件冒泡的性質,事件流開始是從較爲具體的元素接收,一直傳播上不具體的元素上。

首先,假若有一個列表 ul,每一個列表元素 li 點擊會觸發事件處理程序,顯然,若是一個一個地給元素綁定事件, 效率確定很差。

與此同時,當新增一個元素時,事件不見得會綁定成功。一塊兒來看:

<ul id="menu">
	<li class="menu-item">menu-1</li>
	<li class="menu-item">menu-2</li>
	<li class="menu-item">menu-3</li>
	<li class="menu-item">menu-4</li>
</ul>
<input type="button" name="" id="addBtn" value="添加" />

<script> window.onload = function() { var menu = document.getElementById("menu"); var item = menu.getElementsByClassName('menu-item'); for (var i = 0; i < item.length; i++) { item[i].onclick = (function(i) { return function() { console.log(i); } }(i)) } var addBtnEl = document.getElementById("addBtn"); addBtnEl.onclick = function() { var newEl = document.createElement('li'); newEl.innerHTML = "menu-new" menu.appendChild(newEl); } } </script>
複製代碼

新增長的 menu-new,點擊發現沒有反應,說明事件沒有綁定進去,可是咱們也並不想,每增長一個新元素,就爲這個新元素綁定事件,重複低效率的工做應當避免去作。

咱們經過事件委託的思路來想,事件流的傳播,目標元素自己依然會有事件,但同時,冒泡出去後,更高層次的 dom 也能處理事件程序。那麼,咱們只須要給高層次節點綁定事件,經過判斷具體是觸發的哪一個子節點,再作相應的事件處理。

<ul id="menu">
	<li class="menu-item">menu-1</li>
	<li class="menu-item">menu-2</li>
	<li class="menu-item">menu-3</li>
	<li class="menu-item">menu-4</li>
</ul>
<input type="button" name="" id="addBtn" value="添加" />

<script> window.onload = function() { var menu = document.getElementById("menu"); menu.onclick = function(event) { var e = event || window.event; var target = e.target || e.srcElement; console.log(e); switch (target.textContent) { case "menu-1": console.log("menu-1 被點擊了"); break; case "menu-2": console.log("menu-2 被點擊了"); break; case "menu-3": console.log("menu-3 被點擊了"); break; case "menu-4": console.log("menu-4 被點擊了"); break; case "menu-new": console.log("menu-new 被點擊了"); break; } } var addBtnEl = document.getElementById("addBtn"); addBtnEl.onclick = function() { var newEl = document.createElement('li'); newEl.innerHTML = "menu-new" menu.appendChild(newEl); } } </script>
複製代碼

menu 列表的每一個子菜單元素的事件都能正確響應,新增的 menu-new 一樣也能正確響應事件。

事件委託的好處在於,咱們不用給每一個元素都一一地手動添加綁定事件,避免重複低效的工做。

其次,事件委託更少得獲取 dom, 初始化元素對象和事件函數,能有效減小內存佔用。

每當將事件程序指定給元素時,html 代碼和 js 代碼之間就創建了一個鏈接,這種鏈接越多,網頁就執行起來越慢,因此事件委託能有效減小鏈接樹,提升網頁性能。

總結

用戶與網頁的交互是經過事件進行的,事件模型分爲事件冒泡和事件捕獲,事件冒泡的兼容性更好,應用更廣,同時經過事件冒泡,能夠創建事件委託,提高網頁性能。

相關文章
相關標籤/搜索