國慶前幾天由於任務比較重,要趕在國慶前把一個進度的任務開發完成,所以也就有點趕,但是卻遇到了一個比較奇怪的Bug,導致了任務比預計的延遲了幾個小時,對此深表遺憾,所以利用國慶的這段時間來補一補這個不足,也為了國慶後能夠更好的完成公司的業務打下基礎
二、大致了解什麼是JavaScript事件流首先這裡先來舉一個例子
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="content"> <div id="btn1"> </div> </div> </body> <script type="text/javascript"> var content=document.getElementById("content"); var btn1=document.getElementById('btn1'); btn1.onclick=function(){ alert("btn1"); }; content.onclick=function(){ alert("content"); } </script> </html>
先不看運行結果:這個時候你可以大膽的猜一猜,這個時候是先彈出btn1,還是先彈出content,這個就涉及到JavaScript事件流的問題(這個我就劇透一下運行的結果是先彈出btn1,後彈出content
從這個問題就引出了一個JS事件流的問題,也就是關於事件觸發順序的問題
三、JS事件流分析
JS事件流最早要從IE和網景公司的浏覽器大戰說起,IE提出的是冒泡流,而網景提出的是捕獲流,後來在W3C組織的統一之下,JS支持了冒泡流和捕獲流,但是目前低版本的IE浏覽器還是只能支持冒泡流(IE6,IE7,IE8均只支持冒泡流),所以為了能夠兼容更多的浏覽器,建議大家使用冒泡流。
JS事件流原理圖如下:
從圖中我們可以知道
1、一個完整的JS事件流是從window開始,最後回到window的一個過程
2、事件流被分為三個階段(1~5)捕獲過程、(5~6)目標過程、(6~10)冒泡過程
3、在冒泡過程中6比7早觸發,也就解釋了上面那題,為什麼btn1,會比content先觸發
然而在有些情況下JS的事件流不會根據上圖這個從捕獲過程到目標過程到冒泡過程這樣去推進的,
從表中我們可以知道在DOM Level 0事件的時候是不支持捕獲事件的,那麼什麼是Level 0呢
四、what is DOM Level
這一章,我們就來探討什麼是DOM Level,先從DOM Level 0說起,以上面的例子為例,JS部分可以這樣改寫為:
var btn1=document.getElementById("btn1"); var content=document.getElementById("content"); btn1.onclick=function(){ alert("btn1_click"); } btn1.onclick=function(){ alert("btn1_click2"); } content.onclick=function(){ alert("content_click"); }
運行的結果是先彈出btn1_click2,然後彈出content_click,從這裡案例我們可以得出這幾點:
1、btn1_click這個事件沒有被觸發,所以在DOM Level 0中的只能一個元素只能綁定一個事件,並且綁定的是最後綁定的那個事件,這個有點像jquery中的html方法一樣,後面的會覆蓋掉前面的內容一樣
2、content_click的觸發也同時也證明了DOM Level 0不會阻止事件冒泡的發生
在DOM Level 0中要為事件解除綁定,我們可以這樣設置
btn1.onclick=null;
將click事件設置為null也就可以解除事件綁定了
接下來��們來說一說什麼是DOM Level 2,DOM Level 2支持事件的冒泡與捕獲,但是由於有些浏覽器的不支持事件捕獲,所以為了更好的兼容更多的浏覽器,建議使用事件冒泡,除非是業務需要在某個事件觸發之前觸發某個事件,那麼就需要使用事件的捕獲。下面我們就來看看什麼是DOM Level 2,JS部分如下
var btn1=document.getElementById("btn1"); var content=document.getElementById("content"); btn1.addEventListener("click",function(){ alert("btn1"); },false); btn1.addEventListener("click",function(){ alert("btn2"); },false); content.addEventListener("click",function(){ alert("content"); },false);
運行的結果是:先彈出btn1,然後彈出btn2,最後彈出content ,運行的結果我們可以分析出來:
DOM Level 2可以在一個元素上面注冊一個事件的時候在注冊另外一個事件,也就是可以同時在一個元素上面存在多個事件
DOM Level 0與DOM Level 2的主要區別去了上面說的一點外還有以下的幾點:
1、DOM Level 2可以在一個元素上面注冊多個事件,但是DOM Level 0就不可以
2、DOM Level 0的兼容性好,可以支持所有的浏覽器,但是DOM Level 2中的addEventListener的這個方法在IE浏覽器是不支持的,IE浏覽器支持attachEvent事件,attachEvent事件支持兩個參數,第一個是事件類型,第二個是執行的函數,DOM Level 0不同於addEventListener,這個在使用的時候要加上on,例如:addEventListener的單擊事件是click,而attachEvent的單擊事件是onclick,由於IE只支持冒泡事件,所以沒有第三個參數
3、DOM Level 2在IE中的綁定事件是attachEvent,解除綁定是detachEvent,在標准的浏覽器綁定事件是addEventListener,解除綁定是removeEventListener
所以為了兼容IE浏覽器和標准的浏覽器,我們需要編寫通用的方法來處理,方法如下:
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; } } };
方法來源於網上,如有錯誤請指出
這個通用的方法在使用的時候要注意的是handler不能在方法裡面直接寫,要在外部將方法封裝好,然後在通過調用外部這個封裝好的方法來實現,因為移除綁定事件的時候也要傳handler事件對應的方法,這個做的一個主要的原因是使用DOM Level 2可以注冊多個事件,但是移除的時候可以指定要移除元素上面的哪個事件,所以需要傳handler參數
講到這個我們就來拓展一下事件裡面的一些相關屬性
我們直接打印事件可以得到如下圖:
上圖中的重要屬性對應的介紹如下所示:
type
: 觸發事件的類型;bubbles
: 表名事件是否冒泡;cancelable
: 表名是否可以取消事件的默認行為;currentTarget
: 當前正在處理事件的元素;target
: 事件的目標元素;defaultPrevented
: 表名是否已經調用了preventDefault()方法;detail
: 與事件相關的細節信息;eventPhase
: 表示事件處理的階段: 1,捕獲階段; 2,處於階段; 3,冒泡階段;trusted
: true表示該事件是浏覽器生成的, false表示是開發人員通過JavaScript創建的;view
: 與事件關聯的抽象視圖, 相當於發生事件的window對象;preventDefault()
: 取消事件的默認行為;stopImmediatePropagation()
: 取消事件的進一步獲取或者冒泡, 同時阻止任何事件處理程序被調用;stopPropagation()
: 取消事件的進一步獲取或者冒泡;
這些屬性雖然在使用的時候比較少涉及,但是關鍵時候有時候還是需要用到的,這裡面有幾個屬性需要特別說明:
一、stopPropagation()與preventDefault()的區別
preventDefault()主要是用來阻止標簽的默認行為
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="content"> <div onclick="alert(1)" id="btn1"> </div> <a id="tag" target="_blank" href="http://www.baidu.com">qweqwe</a> </div> </body> <script type="text/javascript"> var tag=document.getElementById("tag"); tag.addEventListener("click",function(event){ event.preventDefault(); },false); </script> </html>
例如上面的那段代碼使用preventDefault()這個方法就阻止了a標簽的打開新窗口
stopPropagation()這個方法主要是用來阻止事件冒泡的,這個一般在一些特定業務中不需要冒泡的情況下可以使用,示例代碼如下:
var btn1=document.getElementById("btn1"); var content=document.getElementById("content"); btn1.addEventListener("click",function(event){ alert("btn1"); event.stopPropagation(); },false); content.addEventListener("click",function(){ alert("content"); },false);
這一段代碼就阻止了id="btn1"向上級id="content"上面冒泡,打印出來的結果是:彈窗彈出btn1
二、preventDeefault()和stopPropagation()在IE浏覽器和標准浏覽器上面使用的差異
在標准浏覽器的使用方法如第一點所示,在IE浏覽器上面是event事件是沒有preventDefault()這個屬性的,所以在IE上,我們需要設置的屬性是returnValue
window.event.returnValue=false
stopPropagation()在標准浏覽器上面也是如第一點所示,在IE上面的用法如下:
event.cancelBubble=true
三、target與currentTarget
target這個屬性指向的是目標過程中的DOM對象,currentTarget這個指向的是當前的對象,具體內容跟this一樣,所以當this指向的是目標的時候,target與currentTarget相同,這個理解即可,在實際業務中這個知識點使用頻率較少
接下來我們就來說一說DOM Level 2,這一部分主要是一些復雜的交互事件,包括鼠標響應事件,鍵盤響應事件,具體用法跟click事件類似,下面的這幾個屬性的對比是需要關注的。
clientX
,clientY
: 這兩個屬性表示鼠標光標相對浏覽器的水平和垂直坐標;pageX
,pageY
: 這兩個屬性表示鼠標光標相對文檔的水平和垂直坐標; IE8及更早版本不支持;screenX
,screenY
: 這兩個屬性表示鼠標光標相對整個屏幕的水平和垂直坐標;
五、HTML5新增的事件
在HTML5到來的時候新增加了一些事件,其中在這裡就一些關鍵的事件進行講解,洗完起到一個拋磚引玉的作用
1、 contextmenu事件
這個事件是當鼠標右擊的時候觸發的,但是觸發這個屬性的時候默認的行為也會被觸發,所以需要通過preventDefault()方法來阻止
2、beforeunload事件
beforeunload在頁面卸載之前觸發, 該事件會彈出一個對話框, 詢問是否確定離開, 事件的returnValue屬性表示對話框顯示的文字內容;
3、hashchange事件
該事件當URL中的hash值改變時觸發, 通常用於Ajax應用中利用URL參數保存導航信息;這個在前端路由的制作中是非常有用得
除此之外還有很多的事件,但是可能在實際業務中使用得比較少,所以不在這裡說明
六、事件委托
不知道大家在平時的使用的時候有沒有遇到過這樣的一種情況,如果事件涉及到更新HTML節點或者添加HTML節點的時候,就會出現這樣的一種情況,新添加的節點無法綁定事件,更新的節點也是無法綁定事件,表現的行為是無法觸發事件
例如:
<ul id='myLink'> <li id='a'> apple </li> <li id='b'> banana </li> <li id='c'> orange </li> </ul>
如果在上面的ul中添加一個新的li標簽(<li id="d">four</li>),那麼新增加的就會無法觸發
var f = document.getElementById('myLink'); f.onclick = function(e) { console.log(e.target.innerHTML); };
12 個非常有用的 JavaScript 技巧 http://www.linuxidc.com/Linux/2016-05/131297.htm