- “通過事件機制,可以將類設計為獨立的模塊,通過事件對外通信,提高了程序的開發效率。”
- 可以把多個關聯但邏輯復雜的操作利用自定義事件的機制靈活地控制好
對象之間通過直接方法調用來交互
1)對象A直接調用對象B的某個方法,實現交互;直接方法調用本質上也是屬於一種特殊的發送與接受消息,它把發送消息和接收消息合並為一個動作完成;
方法調用方和被調用方被緊密耦合在一起;因為發送消息和接收消息是在一個動作內完成,所以無法做到消息的異步發送和接收;
2)對象A生成消息->將消息通知給一個事件消息處理器(Observable)->消息處理器通過同步或異步的方式將消息傳遞給接收者;
這種方式是通過將消息發送和消息接收拆分為兩個過程,通過一個中間者來控制消息是同步還是異步發送;
在消息通信的靈活性方面比較有優勢,但是也帶來了一定的復雜度。但是復雜度一般可以由框架封裝,消息的發送方和接收方仍然可以做到比較簡單;
總的來說就是一種松耦合的處理,2個對象之間有太多緊密的直接關聯,應該要考慮通過消息通信解耦,從而提高應用程序的可維護性和重用性
在JS中,消息的通知是通過事件表達的,當代碼庫增長到一定的規模,就需要考慮將行為和自定義事件進行解耦。
了解自定義事件的概念
- 類似DOM的行為:在DOM節點(包括document對象)監聽並觸發自定義事件。這些事件既可以冒泡,也可以被攔截。這正是Prototype、jQuery和MooTools所做的。如果事件不能擴散,就必須在觸發事件的對象上進行監聽。
- 命名空間:一些框架需要你為你的事件指定命名空間,通常使用一個點號前綴來把你的事件和原生事件區分開。
- 自定義額外數據:JavaScript框架允許你在觸發自定義事件時,向事件處理器傳送額外的數據。jQuery可以向事件處理器傳遞任意數量的額外參數。
- 通用事件API:只用Dojo保留了操作原生DOM事件的正常API。而操作自定義事件需要特殊的發布/訂閱API。這也意味着Dojo中的自定義事件不具有DOM事件的一些行為(比如冒泡)。
- 聲明:我們往往需要在預定義的事件中加入一些特殊的變化(例如,需要Alt鍵按下才能觸發的單擊事件),MooTools運行你定義此類自定義事件。此類事件需要預先聲明,即便你只是聲明他們的名字。任何未聲明的自定義事件不會被觸發。
一、jQuery自定義事件
jQuery的自定義事件是通過on和one綁定的,然后再通過trigger來觸發這個事件
如有三種情況需要分別處理:
- 用戶提交空值
- 用戶提交的用戶名不存在
- 用戶提交的用戶名存在
jQuery 提供的自定義事件可以引入語義,很好地解決問題
//1. 定義自定義事件 $('#username').on('blank.username', function() { console.log('請不要留空'); }); $('#username').on('notExist.username', function() { console.log('用戶名不存在'); }); $('#username').on('success.username', function() { console.log('用戶名存在'); }); //2. 觸發自定義事件 $('.js-submit').on('click', function() { var username = $('#username').val(); username = $.trim(username); if (username === '') { $('#username').trigger('blank.username'); // 如果 username 為空值,則觸發 blank.username 事件 } $.post(url, {username: username}, function(data) { var res = data; if (res.retcode === -1) { $('#username').trigger('notExist.username'); // 如果用戶不存在,則觸發 notExist.username 事件 } else if (res.retcode === 0) { $('#username').trigger('success.username'); // 如果用戶存在,則觸發 sucess.username 事件 } }); });
trigger需要處理的問題
1.模擬事件對象,用戶模擬處理停止事件冒泡
triger()方法觸發事件后,會執行瀏覽器默認操作。例如:
$("input").trigger("focus");
以上代碼不僅會觸發為input元素綁定的focus事件,也會使input元素本身得到焦點(瀏覽器默認操作)。
如果只想觸發綁定的focus事件,而不想執行瀏覽器默認操作,可以使用jQuery中另一個類似的非冒泡式方法-triggerHandler()方法。
$container.one("focus",function(){
.....
});
$("input").triggerHandler("focus");
該方法會觸發input元素上綁定的特定事件,同時取消瀏覽器對此事件的默認操作,即文本框指觸發綁定的focus事件,不會得到焦點。
請注意這里使用了jQuery 的one 來代替on。這兩者的區別在於,one 在觸發處理器之后會自動將其刪除。
2.區分事件類型,觸發標准的瀏覽器事件 和 自定義事件名綁定的處理程序。
解決方法:事件名稱+命名空間
p4.on('click.aaa.ccc',function(e,vv,c){ console.log('p4') }) p4.trigger('click.aaa')
二、javascript的自定義事件
1. 簡單的自定義事件
自定義事件到激發這個事件,需要document.createEvent(),event.initEvent(),element.dispatchEvent()這三部,分別是創建事件對象,初始化事件對象,觸發事件
<div id="testBox"></div> // 1. 創建事件 var evt = document.createEvent('HTMLEvents'); // 2. 定義事件類型,事件初始化 evt.initEvent('customEvent', true, true); // 3. 在元素上監聽事件,綁定監聽 var obj = document.getElementById('testBox'); obj.addEventListener('customEvent', function(){ console.log('customEvent 事件觸發了'+event.type); }, false);
- console 中輸入 obj.dispatchEvent(evt),可以看到 console 中輸出“customEvent 事件觸發了”,表示自定義事件成功觸發
- 遺憾的是在 IE8 及以下版本的 IE 中並不支持document.createEvent()的方法,IE支持的 document.createEventObject()和event.fireEvent()方法,但是經過測試,fireEvent並不能用於自定義事件,傳給它的參數只能是在IE已經定義了的事件,fireEvent 只支持標准事件的觸發。
function foo1(){ addLog("foo1 is excute"); } function foo2(){ addLog("the id is "+idChange.getId()+" now!"); } if(document.createEvent){ //This is for the stand browser. var ev=document.createEvent('HTMLEvents'); ev.initEvent('fakeEvent',false,false); document.addEventListener("fakeEvent",foo1,false); document.addEventListener("fakeEvent",foo2,false); }else if(document.attachEvent){ //This is for the damn IE document.documentElement.fakeEvents = 0; // an expando property document.documentElement.attachEvent("onpropertychange", function(event) { if (event.propertyName == "fakeEvents") { foo1(); } }); document.documentElement.attachEvent("onpropertychange",function(event){ if(event.propertyName == "fakeEvents"){ foo2(); } }); } function addLog(log){ var logDiv=document.getElementById('log'); var p=document.createElement("p"); p.appendChild(document.createTextNode(log)); logDiv.appendChild(p); } var idChange=function(){ var id=1; return {getId:function(){return id;}, setId:function(a){ id=a; if(document.dispatchEvent) document.dispatchEvent(ev); else if(document.attachEvent) document.documentElement.fakeEvents++; //This for IE }} }();
2. 一個完整的事件機制
這個機制支持標准事件和自定義事件的監聽,移除監聽和模擬觸發操作。需要注意的是,為了使到代碼的邏輯更加清晰,這里約定自定義事件帶有 'custom' 的前綴(例如:customTest,customAlert),demo

/** * @description 包含事件監聽、移除和模擬事件觸發的事件機制,支持鏈式調用 * @author Kayo Lee(kayosite.com) * @create 2014-07-24 * */ (function( window, undefined ){ var Ev = window.Ev = window.$ = function(element){ return new Ev.fn.init(element); }; // Ev 對象構建 Ev.fn = Ev.prototype = { init: function(element){ this.element = (element && element.nodeType == 1)? element: document; }, /** * 添加事件監聽 * * @param {String} type 監聽的事件類型 * @param {Function} callback 回調函數 */ add: function(type, callback){ var _that = this; if(_that.element.addEventListener){ /** * @supported For Modern Browers and IE9+ */ _that.element.addEventListener(type, callback, false); } else if(_that.element.attachEvent){ /** * @supported For IE5+ */ // 自定義事件處理 if( type.indexOf('custom') != -1 ){ if( isNaN( _that.element[type] ) ){ _that.element[type] = 0; } var fnEv = function(event){ event = event ? event : window.event if( event.propertyName == type ){ callback.call(_that.element); } }; _that.element.attachEvent('onpropertychange', fnEv); // 在元素上存儲綁定的 propertychange 的回調,方便移除事件綁定 if( !_that.element['callback' + callback] ){ _that.element['callback' + callback] = fnEv; } // 標准事件處理 } else { _that.element.attachEvent('on' + type, callback); } } else { /** * @supported For Others */ _that.element['on' + type] = callback; } return _that; }, /** * 移除事件監聽 * * @param {String} type 監聽的事件類型 * @param {Function} callback 回調函數 */ remove: function(type, callback){ var _that = this; if(_that.element.removeEventListener){ /** * @supported For Modern Browers and IE9+ */ _that.element.removeEventListener(type, callback, false); } else if(_that.element.detachEvent){ /** * @supported For IE5+ */ // 自定義事件處理 if( type.indexOf('custom') != -1 ){ // 移除對相應的自定義屬性的監聽 _that.element.detachEvent('onpropertychange', _that.element['callback' + callback]); // 刪除儲存在 DOM 上的自定義事件的回調 _that.element['callback' + callback] = null; // 標准事件的處理 } else { _that.element.detachEvent('on' + type, callback); } } else { /** * @supported For Others */ _that.element['on' + type] = null; } return _that; }, /** * 模擬觸發事件 * @param {String} type 模擬觸發事件的事件類型 * @return {Object} 返回當前的 Kjs 對象 */ trigger: function(type){ var _that = this; try { // 現代瀏覽器 if(_that.element.dispatchEvent){ // 創建事件 var evt = document.createEvent('Event'); // 定義事件的類型 evt.initEvent(type, true, true); // 觸發事件 _that.element.dispatchEvent(evt); // IE } else if(_that.element.fireEvent){ if( type.indexOf('custom') != -1 ){ _that.element[type]++; } else { _that.element.fireEvent('on' + type); } } } catch(e){ }; return _that; } } Ev.fn.init.prototype = Ev.fn; })( window );
參考:
- http://www.cnblogs.com/aaronjs/p/3452279.html
- http://kayosite.com/javascript-custom-event.html
- http://dean.edwards.name/weblog/2009/03/callbacks-vs-events/