本文是 重溫基礎 系列文章的第二十篇。html
這是第三個基礎系列的第一篇,歡迎持續關注呀!
重溫基礎 系列的【初級】和【中級】的文章,已經統一整理到個人【Cute-JavaScript】的JavaScript基礎系列中。git
今日感覺:電影有時候看的是緣分。github
系列目錄:web
本章節複習的是JS中的事件,事件冒泡捕獲代理模擬等等。瀏覽器
前置知識:
JavaScript與HTML的交互式經過事件來實現的,是文檔或瀏覽器窗口中發生的一些特定的交互瞬間。閉包
事件流描述的是從頁面中接收事件的順序,一般有這樣兩種徹底相反的事件流概念:事件冒泡流(IE團隊提出)和事件捕獲流(網景團隊提出)。dom
冒泡事件(Event Bubbling):事件開始時由最具體的元素接收(文檔中嵌套層次最深的那個節點),而後逐層向上傳播到較爲不具體的節點(文檔),看下示例代碼:svg
<!DOCTYPE html> <html> <head> <title>leo 事件冒泡</title> </head> <body> <div id="leo">點擊</div> </body> </html>
點擊頁面中<div>
元素,這個click
事件就會按照下面順序傳播:函數
<div>
<body>
<html>
document
因而可知,元素綁定的事件會經過DOM樹向上傳播,每層節點都會發生,直到document對象
,如圖展現了冒泡過程:
post
事件捕獲(Event Capturing):讓不太具體的節點更早接收事件,而最具體的節點最後接收事件,即在事件到達預約目標以前捕獲到,看下示例代碼(HTML代碼和前面同樣),事件捕獲的過程是這樣的:
document
<html>
<body>
<div>
看得出,document對象
最新接收事件,而後沿DOM樹依次向下,直到最後的實際目標<div>
元素,如圖展現了捕獲過程:
注意:因爲老版本的瀏覽器不支持,所以不多人使用事件捕獲,不過若是特殊需求仍是可使用事件捕獲,建議仍是使用事件冒泡。
「DOM2級事件」規定的事件流包含三個階段:事件捕獲階段,處於目標階段和事件冒泡階段。
事件捕獲爲截獲事件提供機會,而後實際的目標接收到事件,最後事件冒泡,對事件做出響應。按照前面的HTML代碼,整個流程是這樣的:
在DOM事件流中,實際目標(<div>
元素)在捕獲階段不接收事件,即在捕獲階段,事件從document對象
到<html>
再到<body>
後就中止,進入「處於目標」階段,事件在<div>
元素上發生,而後才進入冒泡階段,將事件傳回給文檔。
注意:目前主流瀏覽器都支持DOM事件流,只有IE8和以前版本不支持。
事件處理,即響應某個事件。咱們把事件處理的函數,稱爲「事件處理程序」。
事件處理程序的名稱通常都以on
開頭,如click
事件的事件處理程序就是onclick
,load
事件的事件處理程序就是onload
。
咱們將事件處理程序,分爲這麼幾類:
某個元素支持的事件,均可以用一個與相應事件處理程序同名的HTML特性來指定,這個特性的值應該是可以執行的JavaScript代碼。好比:
<input type="button" value="點擊" onclick="alert('hello leo');">
也能夠把須要執行的具體事件單獨定義出來,能夠放置與單獨.js
文件,也能夠在文檔內用<script>
標籤引入:
function fun(){ alert('hello leo'); }
<input type="button" value="點擊" onclick="fun()">
咱們經過這樣指定事件處理程序,能夠有一個局部變量event
來獲取事件對象自己,在這個函數內部,this
值等於這個變量event
。
<input type="button" value="點擊" onclick="fun(event)">
另外,HTML中指定事件處理程序,會有2個缺點:
經過賦值形式,將一個函數賦值給一個事件處理程序屬性。每一個元素(包含window
和document
)都有本身的事件處理屬性,這些屬性一般所有小寫,如onclick
,將這種屬性的值設置成一個函數,就能夠指定事件處理程序:
var leo = document.getElementById('leo'); leo.onclick = function(){ alert('hello leo!'); }
使用DOM0級方法指定事件處理程序,被認爲是元素的方法。此時的事件處理程序是在元素的做用域執行,那麼,this就引用當前元素,能夠訪問元素的任何屬性和方法:
var leo = document.getElementById('leo'); leo.onclick = function(){ alert(this.id); // "leo" }
咱們也能夠經過設置事件處理程序屬性來刪除DOM0級的事件處理程序。
leo.onclick = null;
有2個方法:
addEventListener()
removeEventListener()
全部的DOM節點都包含這兩個方法,而且它們都接收三個參數:
var leo = document.getElementById('leo'); leo.addEventListener('click',function(){ alert(this.id); // "leo" },false);
與DOM0級方法同樣,這裏的事件處理程序也會是在元素的做用域中執行,所以this也是指向元素,能夠訪問元素的任何屬性和方法。
使用DOM2級方法,能夠添加多個事件處理程序,並按照添加順序觸發:
var leo = document.getElementById('leo'); leo.addEventListener('click',function(){ alert(this.id); // "leo" },false); leo.addEventListener('click',function(){ alert('hello leo!'); // "hello leo!" },false);
注意:經過addEventListener()
添加的事件只能經過removeEventListener()
移除,而且二者傳入的參數一致,這就意味着經過addEventListener()
添加的匿名函數不能被移除,看下面代碼:
var leo = document.getElementById('leo'); leo.addEventListener('click',function(){ alert(this.id); // "leo" },false); // 沒有效果 leo.removeEventListener('click',function(){ // do some thing },false);
沒有效果是由於這兩個方法傳入的函數,是徹底不一樣的,爲了達到刪除事件處理程序的效果,咱們能夠將處理函數單獨定義出來:
var leo = document.getElementById('leo'); var fun = function(){ alert(this.id); } leo.addEventListener('click',fun,false); // 有效果 leo.removeEventListener('click',fun,false);
IE實現兩種方法: attachEvent()
和detachEvent()
。這兩個方法接收相同的兩個參數:事件處理程序名稱和事件處理程序函數。
因爲IE8和更早版本只支持事件冒泡,因此經過attachEvent()
添加的事件處理程序會被添加到冒泡階段。
var leo = document.getElementById('leo'); leo.attachEvent('onclick',function(){ alert('hello leo'); // "leo" },false); // attachEvent也支持添加多個事件處理程序 leo.attachEvent('onclick',function(){ alert('hello world'); // "leo" },false);
注意:這裏的第一個參數是onclick
而不是DOM的addEventListener()
的click
。
IE的attachEvent()和DOM0級方法區別:
二者事件處理程序的做用域不一樣。
this
指向window
。和DOM0級方法同樣,detachEvent()
只能移除使用attachEvent()
添加的方法,爲了不沒法移除,也是須要將處理的函數單獨定義出來:
var leo = document.getElementById('leo'); var fun = function(){ alert(this.id); } leo.attachEvent('onclick',fun,false); // 有效果 leo.detachEvent('onclick',fun,false);
在作跨瀏覽器事件處理程序,咱們能夠有兩種方式:
咱們單獨受寫一個處理方法也不難,只需關注好事件冒泡階段,咱們能夠建立一個方法,區分使用DOM0級方法、DOM2級方法或IE方法便可,默認採用DOM0級方法。
var my_event = { addMyEvent: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; } }; removeMyEvent: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; } } }
當觸發一個DOM上的事件時,都會產生一個事件對象event
,並做爲參數傳入事件處理程序,這個對象包含全部與事件相關的信息。包括致使事件的元素、事件類型等其餘信息。
不管指定事件處理程序時使用什麼方法(DOM0級方法或DOM2級方法),都會傳入event
對象:
var leo = document.getElementById('leo'); leo.onclick = function(event){ alert(event.type); // 'click' } leo.addEventListener('click',function(event){ alert(event.type); // 'click' },false);
event
對象包含與建立它的特定事件相關的屬性和方法,經常有以下成員:
咱們可使用event
中的對象和方法:
var leo = document.getElementById('leo'); leo.onclick = function(event){ // 只有當 event 中的 cancelable 屬性爲true的事件 event.preventDefault(); }
經過調用event.stopPropagation()
方法避免彈框出現兩次。
var leo = document.getElementById('leo'); leo.onclick = function(event){ alert('leo'); event.stopPropagation(); } document.body.onclick = function(event){ alert('hello leo'); }
訪問IE中的事件對象event
,方法有多種,取決於事件處理程序的方法:
window.event
var leo = document.getElementById('leo'); leo.onclick = function(){ var event = window.event; alert(event.type); // 'click' }
attachEvent
方法,event
做爲參數傳入(也可使用window.event
)var leo = document.getElementById('leo'); leo.attachEvent('onclick',function(event){ alert(event.type); // 'click' },false);
雖然DOM和IE中event
對象不一樣,但咱們也能夠和前面的 跨瀏覽器事件處理程序 處理同樣,經過他們之間的區別,實現映射:
var my_event = { myAddFun : function(element, type, handler){ // do some thing }, // 返回對event的引用 getMyEvent : function(event){ return event?event:window.event; }, // 返回事件的目標 getMyTarget : function(event){ return event.target || event.srcElement; }, // 取消事件的默認行爲 preventDefault : function(event){ if(event.preventDefault){ event.preventDefault(); }else { event.returnValue = false; } }, myRemoveFun : function(element, type, handler){ // do some thing }, // 阻止事件流 stopPropagetion : function(event){ if(event.stopPropagetion){ event.stopPropagetion(); }else { event.cancelBubble = true; } } } var leo = document.getElementById('leo'); leo.onclick = function(event){ alert('leo'); event = my_event(event); my_event.stopPropagation(event); } leo.onclick = function(event){ alert('hello world'); }
Web瀏覽器中的事件類型有不少,DOM3級事件規定有如下幾類事件類型:
具體每一個方法的詳細介紹,能夠查看**W3school HTML DOM Event 對象**
或者查看《JavaScript高級程序設計(第三版)》的第362頁開始。
我後面會單獨整理一篇,介紹這些事件的文章。
簡單理解就是講一個響應事件委託到另外一個元素。
事件委託利用事件冒泡,指定一個事件處理程序來管理某一類型的全部事件,好比咱們經過一個函數來代替其餘三個函數:
<ul id="btn"> <li id="btn1">按鈕1</li> <li id="btn2">按鈕2</li> <li id="btn3">按鈕3</li> </ul>
var btn1 = document.getElementById('btn1'); var btn2 = document.getElementById('btn2'); var btn3 = document.getElementById('btn3'); // my_event 在前面定義了 my_event.myAddFun(btn1, 'click', function(event){ alert('btn1點擊'); }); my_event.myAddFun(btn2, 'click', function(event){ alert('btn2點擊'); }); my_event.myAddFun(btn3, 'click', function(event){ alert('btn3點擊'); });
下面咱們在DOM樹層級更高的元素上添加一個事件處理函數,來作事件委託,咱們這麼重寫這段代碼:
var btn = document.getElementById('btn'); my_event.myAddFun(btn, 'click', function(event){ event = my_event.getMyEvent(event); var target = my_event.getMyTarget(event); switch(target.id){ case "btn1": alert('btn1點擊'); break; case "btn2": alert('btn2點擊'); break; case "btn3": alert('btn3點擊'); break; } });
最適合採用事件委託技術的事件包括:click
/mousedown
/mouseup
/keyup
/keydown
/keypress
,雖然mouseover
和mouseout
事件也有冒泡,但由於很差處理它們而且常常須要從新計算元素位置。
能夠看出,事件委託有如下優勢:
JavaScript的事件模擬主要用來在任意時刻觸發特定事件。
在document
對象上使用createEvent()
方法建立一個event
對象。
createEvent()
接收一個參數,即要建立的事件類型的字符串。
DOM2級中,全部這些字符串都使用英文複數形式,DOM3級中都變成單數,也能夠是下面中的字符串:
UIEvent
)MouseEvent
)MutationEvent
)HTMLEvent
)建立event
以後,咱們須要使用dispatchEvent()
方法去觸發這個事件,須要傳入一個參數,參數是須要觸發事件的event對象。
全部支持事件的DOM節點都支持這個方法。事件觸發以後,事件就能照樣冒泡並引起響應事件處理程序的執行。
使用createEvent()
方法傳入MouseEvents
建立一個鼠標事件,返回的對象有一個initMouseEvent()
方法,用於指定與該鼠標事件相關的信息,有15個參數:
click
document.defaultView
mouseover
和mouseout
時使用案例:
var btn = document.getElementById('btn'); var myEvent = document.createEvent('MouseEvents'); myEvent.initMouseEvent( 'click', true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null ) btn.dispatchEvent(myEvent);
DOM3級規定,使用createEvent()
方法傳入KeyboardEvent
建立一個鍵盤事件,返回的對象有一個initKeyEvent()
方法,有8個參數:
keydown
document.defaultView
keypress
事件,所以只能用這個方式來模擬keyup
/keydown
事件。模擬按住Shift和A鍵的案例:
var btn = document.getElementById('btn'); var myEvent; // 以DOM3級方式建立 if(document.implementation.hasFeature('KeyboardEvents', '3.0')){ myEvent = document.createEvent('KeyboardEvent'); myEvent.initKeyboardEvent( 'keydown', true, true, document.defaultView, 'a', 0, 'Shift', 0 ); } btn.dispatchEvent(myEvent);
如模擬變更事件和HTML事件。
經過createEvent()
傳入MutationEvents
參數建立,返回一個initMutationEvent()
方法,這個方法接收參數包括:type
/bubbles
/cancelable
/relatedNode
/preValue
/newValue
/attrName
/attrChange
,下面模擬一個案例:
var btn = document.getElementById('btn'); var myEvent = document.createEvent('MutationEvents'); myEvent.initMutationEvent( 'DOMNodeInserted', true, false, someNode, '', '', '', 0 ); btn.dispatchEvent(myEvent);
經過createEvent()
傳入HTMLEvents
參數建立,返回一個initEvent()
方法,下面模擬一個案例:
var btn = document.getElementById('btn'); var myEvent = document.createEvent('HTMLEvents'); myEvent.initEvent('focus', true, false); btn.dispatchEvent(myEvent);
經過createEvent()
傳入CustomEvent
參數建立,返回一個initCustomEvent()
方法,有4個參數:
keydown
event
對象的detail
屬性中案例:
var btn = document.getElementById('btn'); var myEvent; // my_event在前面定義 2.6 跨瀏覽器事件處理程序 my_event.addMyEvent(btn, 'myevent', function(event){ alert('btn detail ', event.detail); }); my_event.addMyEvent(document, 'myevent', function(event){ alert('document detail ', event.detail); }); // 以DOM3級方式穿件 if(document.implementation.hasFeature('CustomEvents', '3.0')){ myEvent = document.createEvent('CustomEvent'); myEvent.initCustomEvent( 'myevent', true, false, 'hello leo', ); btn.dispatchEvent(myEvent); }
IE8及以前的版本模擬事件和DOM中模擬思路類似:想建立event對象
再指定信息,最後觸發。
區別在於,IE中使用document.createEventObject()
方法建立event對象
,而且不接收參數,返回一個通用event對象
,咱們要作的就是給這個event對象
添加信息,最後在目標上調用fireEvent()
方法,並接受兩個參數(事件處理程序的名稱和event對象
)。
在調用fireEvent()
方法會自動添加srcElement
和type
屬性,咱們須要手動添加其餘屬性,下面模擬一個click事件:
var btn = document.getElementById('btn'); var myEvent = document.createEventObject(); myEvent.screenX = 100; myEvent.screenY = 0; myEvent.clientX = 100; myEvent.clientY = 0; myEvent.ctrlKey = false; myEvent.altKey = false; myEvent.shiftKey = false; myEvent.button = 0; btn.fireEvent('onclick', event);
本部份內容到這結束
Author | 王平安 |
---|---|
pingan8787@qq.com | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推薦 | https://github.com/pingan8787/Leo_Reading/issues |
JS小冊 | js.pingan8787.com |