Javascript自定義事件,其本質就是觀察者模式(又稱訂閱/發布模式),它的好處就是將綁定事件和觸發事件相互隔離開,並且可以動態的添加、刪除事件。
下面通過實例,一步一步構建一個具體的Javascript自定義事件對象。
如:我有一個action1函數,我想每次在執行完action1後,觸發另一個函數service1,那麼代碼我們可以這麼寫:
//服務service1
function service1(){
}
//函數action1
function action1(){
//other things
//then 啟動service1
service1();
}
Good,但是現在想法變了,我想在action1完成後,不僅觸發service1,還要觸發service2和service3。
按照剛才的思路,在函數action1完成後,順帶加上它們就是了。
如下:
function service1(){}
function service2(){}
function service3(){}
function action1(){
//other things
service1();
service2();
service3();
}
但,想法又再次發生波動,在執行完action1函數後,我突然想動態添加一個service4,且,發現service2似乎毫無意義,我不想觸發了,怎麼辦呢?
你可能會說去掉service2,然後在action1後面加入service4不就完了嗎?
但是,在真正的項目開發代碼日益劇增的情況下,談何容易,還要去找到相關代碼進行操作。
那怎麼辦呢?
初步想法,定義一個數組嘛(如:servicearray),用來管理所有的service。
當action1執行到末尾後,遍歷一遍這個數組函數(servicearray),就歐克了嘛。
且,倘若我們不想運行service2了,便將它從這個數組中刪除就好了;倘若想再添加一個新的service,將其追加到servicearray數組中就好了。
如此nice,如下:
var servicearray = [];
function service1(){}
function service2(){}
function service3(){}
//將所有service添加到servicearray中
servicearray.push(service1);
servicearray.push(service2);
servicearray.push(service3);
//del:用於刪除一個指定的service
function del(arr, fn){
for(var i = 0; i < arr.length; i++){
if( arr[i] == fn ){
arr.splice(i,1);
break;
}
}
}
//action1後,執行所有的service
function action1(){
//other things
//遍歷serviceaary,執行所有service函數。(servicearray在action1內)
for(var i =0; i < servicearray.length; i++){
servicearray[i]();
}
}
//添加service4
function service4(){}
servicearray.push(service4);
//刪除service2
del(servicearray, service2);
上面代碼挺歐克的,但,復用性一點都不強,且servicearray與action你中有我,我中有你,不好。我們再來優化優化。
代碼如下:
var servicearray = [];
function service1(){}
function service2(){}
function service3(){}
servicearray.push(service1);
servicearray.push(service2);
servicearray.push(service3);
function del(arr, fn){
for(var i = 0; i < arr.length; i++){
if( arr[i] == fn ){
arr.splice(i,1);
break;
}
}
}
//添加一個service4
function service4(){}
servicearray.push(service4);
//刪除一個service2
del(servicearray, service2);
//添加一個觸發函數hanldeAction,分離action與service
function hanldeAction(actionName,serviceArr){
if(typeof actionName === 'function'){
actionName();
for(var i =0; i < serviceArr.length; i++){
serviceArr[i]();
}
}
}
//執行
handleAction(action1,servicearray);
上面的代碼和回調函數有異曲同工之處,因為我們想達到的效果是在 action執行完成之後,運行一系列service嘛。
但,我現在改變想法了,我想在action執行之前執行一系列service呢,或者action中呢。看來得改hanldeAction回調函數啊,這放在項目中反復修改,顯然不行。
所以,我們得讓其更強大才好。(我想讓它在什麼地方執行就執行)
如下:
function service1(){}
function service2(){}
function service3(){}
var servicearray = [];
servicearray.push(service1);
servicearray.push(service2);
servicearray.push(service3);
function del(arr, fn){
for(var i = 0; i < arr.length; i++){
if( arr[i] == fn ){
arr.splice(i,1);
break;
}
}
}
//添加一個service4
function service4(){}
servicearray.push(service4);
//刪除一個service2
del(servicearray, service2);
/*
actionObj用於存儲所有action與service關聯的對象。
如:{
action1:[service1,service2],
action2:[...]
}
*/
var actionObj = {};
/*
修改代碼,增加一個actionName與serviceArr關聯事件。
如,action1關聯所有service,這樣再結合下方的trigger事件就完美了
Params:
actionName --> actionObj的屬性
serviceArr --> 包含所有與actionName相關的service數組
*/
function addAction(actionName, serviceArr){
if(typeof actionObj[actionName] === 'undefined' ){
actionObj[actionName] = [];
}
if(typeof serviceArr === 'object'){
actionObj[actionName].push(serviceArr);
}
}
/*
修改代碼,增加一個觸發actionName事件
如,當我想觸發action1中的所有service時,調用trigger(action1)就OK啦
*/
function trigger( actionName ){
var act = actionObj[actionName];
if(act instanceof Array){
for(var i = 0, len = act.length; i < len; i++){
for(var j =0, arrlen = act[i].length; j++){
((act[i])[j])();
}
}
}
}
上述代碼是可以,但,有個性能問題,addAction中添加到actionObj[actionName]中的是一個數組,其實完全可以將定義的servicearray數組(為了存儲不同的service而聲明的數組)移除,轉而將每個service直接push進actionObj[actionName]聲明的數組中,這樣trigger事件效率也得到了提高,從原來的兩層for循環降到一層for循環。且,我們再加一個刪除service的方法remove。
整理代碼如下:
var actionObj = {};
//修改代碼,增加一個actionName與service函數直接關聯事件
function addAction(actionName, fn){
if(typeof actionObj[actionName] === 'undefined' ){
actionObj[actionName] = [];
}
if(typeof fn === 'function'){
actionObj[actionName].push(fn);
}
}
//修改代碼,增加一個觸發actionName事件
function trigger( actionName ){
var actionarray = actionObj[actionName];
if(actionarray instanceof Array){
for(var i = 0, len = actionarray.length; i < len; i++){
if(typeof actionarray[i] === 'function'){
actionarray[i]();
}
}
}
}
//修改代碼,增加一個刪除actionName中的service事件
function remove(actionName, fn){
var actionarray = actionObj[actionName];
if(typeof actionName === 'string' && actionarray instanceof Array){
if(typeof fn === 'function'){
//清除actionName中對應的fn方法
for(var i=0, len = actionarray.length; i < len; i++){
if(actionarray[i] === fn){
actionObj[actionName].splice(i,1);
}
}
}
}
}
上面的代碼好是好,action與service也互不影響,也完成了它的使命。
使命?
這就是我們一起編寫的自定義事件嘛。是不是很簡單。
哈哈哈,我尼瑪也在代碼中用到設計模式了(觀察者模式)。
一鼓作氣,我們再來優化下上面的代碼。有沒有注意,我們是使用的全局變量,在模塊化開發的大環境下,我們居然在用全局變量,豈不是污染命名空間嘛。再改改。
修改代碼如下:
var EventTarget = function(){
this.listener = {};
}
EventTarget.prototype = {
constructor:EventTarget,
addAction: function(actionName, fn){
if(typeof actionName === 'string' && typeof fn === 'function'){
//如果不存在actionName,就新建一個
if(typeof this.listener[actionName] === 'undefined'){
this.listener[actionName] = [fn];
}
//否則,直接往相應actinoName裡面塞
else{
this.listener[actionName].push(fn);
}
}
},
trigger: function(actionName){
var actionArray = this.listener[actionName];
//觸發一系列actionName裡的函數
if(actionArray instanceof Array){
for(var i = 0, len = actionArray.length; i < len; i++){
if(typeof actionArray[i] === 'function'){
actionArray[i]();
}
}
}
actionArray = null;
},
remove: function(actionName, fn){
var actionArray = this.listener[actionName];
if(typeof actionName === 'string' && actionArray instanceof Array){
if(typeof fn === 'function'){
//清除actionName中對應的fn方法
for(var i=0, len = actionArray.length; i < len; i++){
if(actionArray[i] === fn){
this.listener[actionName].splice(i,1);
}
}
}
}
actionArray = null;
}
};
一個JavaScript自定義事件新鮮出爐。
好了,晚安everyone~