对象之间通过直接方法调用来交互
1)对象A直接调用对象B的某个方法,实现交互;直接方法调用本质上也是属于一种特殊的发送与接受消息,它把发送消息和接收消息合并为一个动作完成;
方法调用方和被调用方被紧密耦合在一起;因为发送消息和接收消息是在一个动作内完成,所以无法做到消息的异步发送和接收;
2)对象A生成消息->将消息通知给一个事件消息处理器(Observable)->消息处理器通过同步或异步的方式将消息传递给接收者;
这种方式是通过将消息发送和消息接收拆分为两个过程,通过一个中间者来控制消息是同步还是异步发送;
在消息通信的灵活性方面比较有优势,但是也带来了一定的复杂度。但是复杂度一般可以由框架封装,消息的发送方和接收方仍然可以做到比较简单;
总的来说就是一种松耦合的处理,2个对象之间有太多紧密的直接关联,应该要考虑通过消息通信解耦,从而提高应用程序的可维护性和重用性
在JS中,消息的通知是通过事件表达的,当代码库增长到一定的规模,就需要考虑将行为和自定义事件进行解耦。
了解自定义事件的概念
一、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);
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 );
参考: