被我拖延了將近一個月的javascript事件模型系列終於迎來了第四篇,也是我計劃中的最後一篇,說來太慚愧了,本來計劃一到兩個星期寫完的,誰知中間遇到了很多事情,公司的個人的,搞的自己心煩意亂浮躁了一段時間,好在最近這些事情都一件件趨於平息,我也有了精力繼續寫文章。
這個自定義事件其實是挺讓我糾結的,首先自己平時從未使用過,只是有一次遇到一個問題有人指點說可以用自定義事件,才對這個東西有了印象。在網上搜“javascript自定義事件”,發現也有不少文章在寫,不過說實話讓我佩服的卻一篇也沒找到,就連張鑫旭大哥寫的漫談javascript自定義事件也把我看的雲裡霧裡。陸續查閱了一些資料後越發覺得自定義事件這個東西真是個雞肋,沒什麼用武之地。這也使我一度想放棄寫這篇文章,但後來自己又進行了一些思考,並有了一些新的想法,所以在此還是寫出來與大家分享。本來想大手一揮書寫標題“javascript自定義事件”,轉而一想這個標題真是被用濫了,滿大街走的都是長一個樣的,誰能認得我呀,親,我跟他們可真的不太一樣哦。所以前面加上了“我所理解的”,並不全是為標題突出,其實真的有很多自己的理解。
一、什麼是自定義事件
這個其實並不難理解,js中有很多的事件,如click(單擊)、dbclick(雙擊)、mouseover(滑鼠移上)等等,大部分是一些滑鼠或鍵盤事件,當然也還有其他的如文件的load,只不過我們平時更加關注前者,因為畢竟是要和使用者打交道嘛,這些可以統稱為DOM事件(都是發生在DOM元素上)。何為自定義DOM事件呢?舉個栗子:元素有單擊、雙擊事件,我現在想定義一個三擊事件,即元素被連續點選了三次,就給它起名字叫tripleclick。這就是一個自定義的事件。再來個栗子:元素的內部html發生變化,我想監聽到此事件,於是可以定義一個htmlchange事件。概念就是這麼回事。
既然有DOM事件,那有沒有非DOM事件呢?動腦筋想一想。有了~前段時間剛好研究過history API,其中點選了瀏覽器後退按鈕會觸發popstate事件,這就是個非DOM事件。那我們可以自定義些什麼非DOM事件呢?再開闊一下思維,javascript好歹也是一門程式語言呀,除了操作DOM、BOM之外還有很多事情可以做呢。比如我寫了一個dog物件,也可以給它自定義個事件bark(狗叫),一旦發生了這個事件,我們可以捕捉到,在onbark處理函式中執行一些操作,如把dog給趕走。
那麼,所謂自定義事件就是起個名字?這就完了?先埋個伏筆,後面來談談我的看法。
二、如何自定義事件
關於實現自定義事件的方式,我搜尋中文的網頁大概也就兩種方式,而且就是那麼幾篇文章被抄來抄去,實在是乏味。總結一下:
第一種方式是自己模擬一個事件結構,其原理是這樣的,我們平時監聽事件的時候其實就是一種觀察者模式,舉個例子吧更明確些。
<input type="button" value="點我" onclick="clickhandler()" /> <script type="text/javascript"> function clickhandler(){ alert('點你怎麼了!'); } </script>
在這裡被觀察的主體就是這個button,有一個handler訂閱了它的點選事件,當被點選時,button會發布自己被點選的訊息,handler接收到訊息便開始執行處理函式。是相當標準的一個觀察者模式。
照著這個思路,我們可以把整個過程用程式碼模擬出來,而不使用瀏覽器的事件機制,讓這個button釋出一點其他的訊息,比如我們霸氣的“三擊”,然後寫一個handler來監聽這個三擊事件即可。具體的實現例子我就不寫了,因為我覺得這個模擬的辦法簡直是太土了,根本拿不上臺面,想研究的可以看下這篇文章http://www.jb51.net/article/33697.htm 儘管我很噁心指令碼之家這種隨便剽竊別人文章的行為,但抱歉我真的找不到出處了。。。
看過了第一種土的掉渣的方式,我們再來看看高階洋氣的寫法。說白了,其實w3c已經定義了標準的自定義事件寫法了。
第二種方式如下:
var e = document.createEvent('Event');//建立一個Event物件e e.initEvent('myevent',true,true);//進行事件初始化 var d1 = document.getElementById('d1');//獲取DOM元素 d1.addEventListener('myevent',function(event){ alert(‘我監聽到了自定義事件’+event.type); },false);//繫結監聽器 d1.dispatchEvent(e);//觸發該事件
使用標準方法還是相當簡單的,首先利用document的createEvent方法可以建立一個事件物件,createEvent接收一個參數列示事件的構造器,如Event、MouseEvent、UIEvent、CustomEvent,至於這些事件類都有哪些這裡就不詳細講了,你可以檢視我之前寫的系列,有提到相關內容可以追蹤連結。然後使用initEvent函式進行事件的初始化,接收的引數分別表示事件的型別、是否冒泡、是否可以用preventDefault()函式禁止預設行為,在這裡你就可以為自定義事件起名字了。然後我們註冊監聽器並觸發事件,這樣d1便能監聽到自己定義的事件了,ok,就這麼簡單!
本來自定義事件的方式就該到此結束了,一個小小的意外,我搜到了一篇國外的文章,看到了如下字樣:
來自mozilla開發者官網,說的就是上面的第二種方式。deprecated?啥意思?google之,藐視的意思!這種方式已經被藐視了哇!竟然還在國內的各網站中被轉來轉去,國外的同仁正在藐視我們。。。不能忍!趕快看看現在都用什麼方式了。
第三種:
var event = new CustomEvent('build', { 'detail': elem.dataset.time });//區別就在這裡~ elem.addEventListener('build', function (e) { ... }, false); elem.dispatchEvent(event);
原來是直接建立Event物件,取代了原來的document.createEvent(),而且事件的初始化工作也在這裡完成了,不必呼叫initEvent()了。嗯~不錯,是能省一行程式碼。探討為什麼要這麼寫也沒什麼意義,我們跟著國際潮流走就是了。現在已經越來越明顯,這個所謂的自定義事件,其實與其他事件是同宗同源,只是名字(型別)不同罷了。
三、自定義事件例項
瞭解了這麼多,你肯定也和我一樣還在困惑,上面的東西都是紙上兵法,這自定義事件到底怎麼用我還是不知道。比如我就想要一個tripleclick(三擊)事件,具體該如何實現呢?下面就來實踐一下,GO~
自定義事件的步驟我總結為“三板斧”,下面開始操練:
① 建立自定義事件
var e = new CustomEvent('tripleclick',{'detail':'somemsg'});//建立自定義事件tripleclick
② 在合適的時機觸發事件
var counter = 0; var d1 = document.getElementById('d1'); d1.onclick = function(){ setTimeout(function(){counter=0;},500); if(++counter==3){ d1.dispatchEvent(e); } }
其實這第二步才是實現tripleclick事件的核心,首先宣告一個計數器,每次元素點選便自增,當累計點選三次的時候將事件派發出去,即觸發事件。為了防止每次點選之間的間隔時間過長,每次點選後由一個延時函式進行清零,保證只有是連續點選才觸發。程式碼不難理解。我也想在這裡說說我的看法,自定義事件不單單是起個自定義名字,還要給這個事件加以描述,定義好它是在什麼樣的情況下發生。
在這裡,tripleclick是依賴於click的,看上去更像是一個邏輯事件,非真正的事件。但由於我們的物件確實是CustomEvent的例項,那它便無疑是一個貨真價實的自定義事件。你可能會擔心難道我們的自定義事件都要依賴於現有的事件?其實也未必,稍後會寫另外一個例子來說明。
③ 為事件註冊監聽函式
d1.addEventListener('tripleclick',function(event){ alert(‘我被三擊了~’); },false);
在此處就可以把我們定義的tripleclick光明正大的寫在addEventListener函式中了。
全部步驟就這些,完整的程式碼如下:
<div id="d1">有本事點我三次</div> <script type="text/javascript"> var d1 = document.getElementById('d1'); d1.addEventListener('tripleclick',function(event){ alert('我被三擊了~'); },false); var e = new CustomEvent('tripleclick',{'detail':'somemsg'}); var counter = 0; d1.onclick = function(){ setTimeout(function(){counter=0;},500); if(++counter==3){ d1.dispatchEvent(e); } } </script>
你可以輕輕抖動三下手指,點選下面這個囂張的div:
有點感覺了吧~不過這個三擊確實有點小兒科,實際上能派上用場的概率基本為0,下面就來整點有用的東西,我們來實現一個htmlchange事件,即元素的內部html發生變化時觸發該事件。這個東西在平時或許還真能用得著。
首先,事件的建立和監聽與上面基本一樣
var e2 = new CustomEvent('htmlchange',{'detail':'somemsg'}); d1.addEventListener('htmlchange',function(event){ alert('檢測到html發生變化!'); },false);
然後,我們需要採用一個定時函式來不斷檢測d1內部的html,當發現與舊值不同時便派發htmlchange事件,程式碼如下:
var oldhtml = d1.innerHTML; setInterval(function(){ if(d1.innerHTML!==oldhtml){ d1.dispatchEvent(e2); oldhtml = d1.innerHTML; } },100);
三板斧完畢,來看看效果:
最慢能在0.1s內作出反應,你也可以把時間設定的更小一些。我們構想一個場景,你寫的一個子頁面A要被別人的頁面B巢狀,並且B會修改A頁面中某個元素的內部html,A在無權干涉B中程式碼的情況下,就可以在自己頁面中定義一個htmlchange事件,監聽B對A的修改並作出處理。怎麼樣,體會到自定義事件的威力了吧。
四、談談自定義事件的用武之地
看了很多紙上談兵的介紹之後我就一直在想,自定義事件的研究和使用為何如此少?它的真正用處到底在哪裡?難道真的是javascript中的雞肋?
在第三節舉出的兩個例項或許能說明一點什麼,至少它能幫我們擴充套件一下DOM事件,在當前已有的事件不能滿足需求時,可以自己定義一個來使用。其他的用途呢?此時我想到了開篇提到的觀察者模式,我們認為瀏覽器對事件的處理是一種觀察者模式,如果反過來呢,我想設計一種觀察者模式,是否可以用自定義事件來實現呢?主體需要釋出的各種訊息通過建立各種自定義事件來實現,對於訊息的訂閱則通過註冊監聽器來實現,豈不是利用現有資源便完成了一個觀察者模式,而不必再寫那麼多的程式碼去模擬。到這裡我又情不自禁的想到了node.js,在node中,用事件驅動來完成程式碼邏輯,其事件是否跟這裡的自定義事件如出一轍?只是個猜測,我對node一知半解,真相也不得而知。
不過至少也可以得出一個結論,自定義事件並非雞肋,站在設計模式或者是設計一個框架的角度來看,它的特性或許真是處理某類問題的靈丹妙藥。