事件是前端之中,很是重要的一個部分。其做用在於對於用戶的各類行爲進行相應。近日打算對於事件系統進行更爲深刻的學習,同時,對於這一部分學習的內容進行一個總結。由於瀏覽器發展至今,事件系統自己已經尤其的複雜了,因此事件這一部份內容可能會將分爲不少章來進行總結。本章將對於事件系統,根據我的的經驗,以及其餘地方學到的東西進行一個概括,給出一些簡單地處理方案,而在後面幾章將會引入經典jquery的源碼進行閱讀。前端
事件系統發展至今,咱們常見的對於事件的綁定方式有三種。jquery
直接將其寫在元素標籤之中。相似用以下的寫法瀏覽器
<div onClick="function">
這種方法能夠說是很是古老的寫法了,不過至今仍是會有人在用。對已如今來講其實並不推薦使用這種方法來綁定事件,其不推薦使用的緣由將在第二種綁定方法之中說明。app
對於第一種onXXX的方法,也採用以下的方法進行綁定dom
el.onclick = function
這種方法其實和第一種綁定方法本質上是同樣的。也就是咱們經常所說的dom0事件。以前在一種中也說過,其實如今並不推薦使用這種方法,緣由以下函數
其實對於該方式綁定事件來講,前面三點就決定了,咱們不能使用該方法進行綁定事件。而對於第四點,dom3的部分新增事件的不支持,其實咱們在平常的使用之中,對於不支持的那些事件,咱們的使用頻率也是很低的。由於許多新事件,可能還不曾進入咱們的視野,就已經被廢棄了。學習
最後就是咱們常說的dom2事件系統了this
不過dom2事件系統,現存的擁有兩套不一樣的API,所以,在下面將分爲兩方。code
ie方面,對於事件的綁定,採用以下的方法對象
el.attachEvent("on"+type,callback)這是微軟對於ie5添加的API(除了事件綁定外,還有相應的解綁,建立,派發等)。他解決了以前採用onXXX方法會致使的只容許一個回調的狀況,支持了對於同種事件多個回調的綁定。可是這套方案,其實並無給前端帶來什麼好處,當你對一個事件系統進行處理的適合,應該能很深的感受到這種方式其帶來的無數問題,以及對於這些問題的解決,會花費不少不少的心思。大體帶來的問題以下
w3c方面,對於事件的綁定,採用以下方案
el.addEventListener(type,callback,[phase])
這個是咱們現代瀏覽器上使用的方法,ie9開始也對於這套API進行了支持,這應該是咱們目前最常爲使用的方案,固然這套方法也擁有他的一些問題。
事件系統是前端之中,極爲核心的一個部分,所以,咱們必須對其種種問題進行一個個的處理。先拋開強大的jquery的事件處理,假若咱們要寫出一個對於事件系統的處理,並將其投入使用,那麼咱們至少應當解決以下的幾個問題。
那麼,既然整理好了問題,咱們如今就能夠開始去解決那些問題。
對於不一樣瀏覽器API的支持問題:
咱們採用條件判斷來進行簡單地實現就好
function addEvent(target,eventName,callback,useCapture){ if(target.addEventListener){ //w3c方法優先 target.addEventListener(eventName,callback,useCapture); }else if(target.attachEvent){ //而後採用ie下方法 target.attachEvent("on"+eventName,callback); }else{ //最後在考慮使用onXXX形式 target["on"+eventName] = callback; } //返回回調函數,方便用於事件解綁 return callback; } function removeEvent(target,eventName,callback,useCapture) { if (target.removeEventListener) { target.removeEventListener(eventName, callback, useCapture); } else if (target.detachEvent) { target.detachEvent("on" + eventName, callback); } else { //onXXX的形式直接將其設置爲null便可 target["on" + eventName] = null; } }
這樣,對於問題1,能夠說算是解決了,而這樣的一個事件註冊函數,對於對事件系統簡易需求的頁面,已經非常足夠了。不過既然提出了那些問題,那麼就一一來進行解決吧。
對於ie下this指向的問題:
說點題外話,關於this的指向,其實很簡單的來講,就是是誰調用的,this就指向誰。比較籠統的總結一下,平常this的指向就兩種狀況,對象中的this,那麼就指向其對應的對象,普通函數中的this,就指向window。然而attachEvent的this指向,倒是指向window的,所以,咱們不得不對其進行修改。實現方式很簡單,使用call或者apply,對於this指向進行修改就能夠了。所以,上面的代碼能夠修改以下。
function addEvent(target,eventName,callback,useCapture){ if(target.addEventListener){ //w3c方法優先 target.addEventListener(eventName,handler,useCapture); }else if(target.attachEvent){ //而後採用ie下方法 target.attachEvent("on"+eventName,handler); }else{ //最後在考慮使用onXXX形式 target["on"+eventName] = handler; } //增長handler函數,在其中對於this指向進行改變,同時,採用handler處理函數來進行事件回調 function handler(){ callback.call(target); } //返回回調函數,方便用於事件解綁 return handler; } function removeEvent(target,eventName,callback,useCapture) { if (target.removeEventListener) { target.removeEventListener(eventName, callback, useCapture); } else if (target.detachEvent) { target.detachEvent("on" + eventName, callback); } else { //onXXX的形式直接將其設置爲null便可 target["on" + eventName] = null; } }
事件對象的差別性問題:
event對象以及對其的處理也是事件綁定之中,要進行的一個重點內容。而具體的處理,咱們將在以前對於this指向處理之中的handler中來一一進行解決。
大體就是對於target,currentTarget,冒泡,取消默認事件這幾部分來進行簡單地處理。修改後的代碼以下
function addEvent(target,eventName,callback,useCapture){ if(target.addEventListener){ //w3c方法優先 target.addEventListener(eventName,handler,useCapture); }else if(target.attachEvent){ //而後採用ie下方法 target.attachEvent("on"+eventName,handler); }else{ //最後在考慮使用onXXX形式 target["on"+eventName] = handler; } //處理傳入的參數ev function handler(event){ //ie下的事件名須要window.event var ev = event || window.event, stopPropagation = ev.stopPropagation, preventDefault = ev.preventDefault; //獲取觸發事件的對象 ie下的ev.srcElement至關於其餘瀏覽器下ev.target ev.target = ev.target || ev.srcElement; //獲取當前事件活動的對象(捕獲或者冒泡階段) ev.currentTarget = ev.currentTarget || target; //取消冒泡的處理 ev.stopPropagation = function(){ if(stopPropagation){ stopPropagation.call(event); }else{ ev.cancelBubble = true; } }; //取消默認事件的處理 ev.preventDefault = function(){ if(preventDefault){ preventDefault.call(event); }else{ ev.returnValue = false; } }; //執行callback函數,而且this指向,同時用flag接收其返回值 var flag = callback.call(target,ev); //處理flag接收到的返回着爲false的狀況 if(flag === false){ ev.stopPropagation(); ev.preventDefault(); } } //返回回調函數,方便用於事件解綁 return handler; }
補充對於匿名函數取綁的問題:
以前採用了return回調函數的方法,同時,在綁定函數時,用一個變量來存儲回調函數,在解綁時再將變量傳入,以達到解綁的目的。
咱們來看一段以下的代碼
var a = addEvent(el,"click", function(){}); removeEvent(el,"click",a);
這種方法,其實對於解綁來講,代碼量是不多的。同時,也不須要在解綁的時候,再對代碼進行修改,將匿名函數變成非匿名,而後在進行操做。固然,這種方法也有些缺陷,匿名函數並不會佔用命名,而這種方案,始終是須要對變量名進行佔用。所以,若是執意於要對於匿名函數進行解綁的話,能夠考慮對匿名函數變爲非匿名的轉換。參考代碼以下
//事件註冊 function addEvent(target,eventName,callback,useCapture){ //壓縮函數的空格 var fnStr = callback.toString().replace(/\s+/g,''); if(!target[eventName+"event"]){ target[eventName+"event"] = {}; } //存儲事件的函數到target[eventName+'event'][fnStr]中 target[eventName+"event"][fnStr] = handler; useCapture = useCapture || false; //高設上的事件註冊簡單兼容 if(target.addEventListener){ target.addEventListener(eventName,handler,useCapture); }else if(target.attachEvent){ target.attachEvent("on"+eventName,handler); }else{ target["on"+eventName] = handler; } //處理傳入的參數ev function handler(event){ //ie下的事件名須要window.event var ev = event || window.event, stopPropagation = ev.stopPropagation, preventDefault = ev.preventDefault; //獲取觸發事件的對象 ie下的ev.srcElement至關於其餘瀏覽器下ev.target ev.target = ev.target || ev.srcElement; //獲取當前事件活動的對象(捕獲或者冒泡階段) ev.currentTarget = ev.currentTarget || target; //取消冒泡的處理 ev.stopPropagation = function(){ if(stopPropagation){ stopPropagation.call(event); }else{ ev.cancelBubble = true; } }; //取消默認事件的處理 ev.preventDefault = function(){ if(preventDefault){ preventDefault.call(event); }else{ ev.returnValue = false; } }; //執行callback函數,而且this指向,同時用flag接收其返回值 var flag = callback.call(target,ev); //處理flag接收到的返回着爲false的狀況 if(flag === false){ ev.stopPropagation(); ev.preventDefault(); } } } //事件取綁(匿名函數) function removeEvent(target,eventName,callback,useCapture){ //壓縮空格 var fnStr = callback.toString().replace(/\s+/g,''), handler; if(!target[eventName+"event"]){ return; } //獲取到存儲的函數 handler = target[eventName+"event"][fnStr]; useCapture = useCapture || false; if(target.removeEventListener){ target.removeEventListener(eventName,handler,useCapture); }else if(target.detachEvent){ target.detachEvent("on"+eventName,handler); }else{ target["on"+eventName] = null; } }
對於ie下執行順序的問題:
不少狀況下,咱們對於事件綁定的順序確定是有要求的,所以不按照順序的執行不少時候是咱們所不想看到的,所以,咱們須要對於回調的執行順序進行一個處理。處理的思路也不算複雜。假若在ie下,咱們對於同一個事件作了多個回調,那麼咱們將對其進行判斷,並將其合併到一個回調之中。簡單來講,就是以下的這種思路
//對於el綁定了a和b兩個回調 el.attachEvent("onclick",a) el.attachEvent("onclick",b) //對兩個回調進行整合處理,而後讓其按順序執行 el.attachEvent("onclick",function(){ a(); b(); })
上面是對於思路的一種抽象,不過當真正開始具體執行的時候,實際上是很複雜的。
簡單來講,這種執行方案,就是將多個函數進行打包,而後丟到一個事件綁定中去執行,而若是採用addEventListener或者attachEvent直接進行綁定的話,不管如何處理,都很難達到只對事件綁定一次的目的。(起碼用這兩個函數,採起直接對元素進行綁定,我沒想到什麼很好的解決方案。)固然,這也並非不能解決的,很早以前Dean Edwards大神的addEvent庫就巧妙的解決了這個問題,他並無採用流行的addeventListener/attachEvent方法,而是直接採用dom0事件系統對其進行了處理,巧妙的利用了dom0事件系統只能綁定一個事件的特性。如今很流行的jquery事件系統,不少思想也是參考的這個庫中的思想。
所以對於執行順序的處理,在上面的事件註冊之中,並無說起到。可是在以後的章節會進行說起。同時,這樣的事件註冊,對於需求不算複雜的頁面,也算是足夠了。
第一章也就到這裏了……