手机版

jQuery技术使任何组件都支持类似DOM的事件管理

时间:2021-11-04 来源:互联网 编辑:宝哥软件园 浏览:

本文介绍了jquery的一个技巧,它可以让任何组件对象都支持类似DOM的事件管理,也就是说,除了调度事件、添加或删除事件侦听器之外,它还可以支持事件冒泡,防止事件的默认行为。在jquery的帮助下,用这个方法管理普通对象的事件和管理DOM对象的事件是完全一样的。虽然最后看到这个小技巧的具体内容时你可能会觉得是这样或者那样,但是我觉得如果把普通的发布-订阅模式的实现改成类似DOM的事件机制,开发出来的组件会有更大的灵活性和可扩展性。而且,这是我第一次用这个方法。

在正式介绍这种技术之前,我得讲一个我之前考虑过的方法,那就是发布-订阅模式,看看它能解决什么问题,有什么问题。

1.发布-订阅模式。

包括书籍在内的很多博客都说,如果javascript想要实现组件的自定义事件,可以采用发布-订阅模式。一开始,我坚定地这么认为,所以我用jquery的$写了一个。回调:

define(function(require,exports,module){ var $=require(' jquery ');var Class=require('。/class ');函数isFunc(f){ return object . prototype . tostring . apply(f)=='[object Function]';}/* * *这个基类可以让普通类具有事件驱动能力*提供类似jq的开关机触发方法,不考虑一个方法和命名空间*示例:* var e=new event base();* e.on('load ',function(){ * console . log(' loaded ');* });* e . trigger(' load ');//loaded * e . off(' load ');*/var event base=Class({ instance members 3360 { init : function(){ this . events={ };//设置$标志。回调作为实例属性,以便子类可以覆盖它。回调_FLAG='唯一';},函数(类型,回调){类型=$。装饰(类型);//如果类型或回调参数无效,如果(!(类型isFunc(回调)))返回;var event=this . events[type];if(!事件){//定义一个新的jq队列,该队列不能添加重复的回调事件=this.events [type]=$。回调(this .回调_ flag);}//向该队列添加回调,可以通过类型访问event.add(回调);},off:函数(类型,回调){type=$。装饰(类型);if(!类型)返回;var event=this . events[type];if(!事件)返回;If (isFunc(callback)) {//如果同时传递了type和callback,则从与type对应的队列中移除event . remove(callback);} else {//否则,删除整个类型对应的队列delete this . events[type];}},trigger : function(){ var args=[]. slice . apply(args),type=args[0];//第一个参数转换为type type type=$。装饰(类型);if(!类型)返回;var event=this . events[type];if(!事件)返回;//使用剩余参数触发与type//对应的回调,并将回调的上下文设置为当前实例event.firewith (this,args . slice(1));}}});返回EventBase});(基于seajs和《详解Javascript的继承实现》中引入的继承库class.js)

只要任何组件继承了这个EventBase,就可以继承它提供的on off触发器方法,完成订阅、发布和取消订阅消息的功能,比如下面我要实现的FileUploadBaseView:

define(function(require,exports,module){ var $=require(' jquery ');var Class=require('。/class ');var EventBase=require('。/EventBase’);Var DEFAULTS={data: [],//要显示数据列表,列表元素必须是对象类型,例如[{url3360' xxx.png'},{ URL 3360 ' yyyy . png ' }]size limit 3360 0 0,//用于限制BaseView中显示的元素数量。0表示对readonly:没有限制false,//用于控制是否允许BaseView中的元素在onBeforeRender: $上添加或删除。noop,///对应beforeRender事件,onRender: $。noop在调用呈现方法之前被触发。//对应于render事件,onbeforemappend: $。noop在调用render方法后被触发。//对应beforeAppend事件,和onappend3360 $。noop在调用append方法之前被触发。//对应追加事件。OnbeforeRemove: $。调用append方法后触发noop,//对应beforeremove事件,onRemove: $。remove事件对应的noop //在remove方法调用之前触发,在remove方法调用之后触发};/* * *数据分析,给每个元素添加唯一标识符_uuid,方便查找*/函数resolved data(CTX,data) {vartime=newdate()。gettime();返回$。map(数据,函数(d){ d . _ uuid=' _ uuid ' time math . floor(math . random()* 100000);});} var file uploadbaseview=Class({ instance members 3360 { init : function(options){ this . base();this.options=this.getOptions(选项);},getOptions:函数(选项){return $。extend({},DEFAULTS,options);},render: function(){},append: function(data){},remove: function(prop){},extend : event base });返回文件上传基础视图;});实际的呼叫测试如下:

在测试中,实例化了一个FileUploadBaseView对象f,并设置了它的name属性。通过on方法添加了一个与hello相关的侦听器。最后,hello的侦听器通过trigger方法被触发,并传递了两个附加参数。在监听器中,不仅可以通过监听器的函数参数访问trigger传递的数据,还可以通过这个函数访问f对象。

从目前的结果来看,这个方法似乎还不错,但是当我想继续实现FileUploadBaseView的时候,就遇到了问题。看看我在设计这个组件时与订阅相关的选项:

我最初的设计是这些订阅是成对定义的,一对订阅对应一个实例方法。例如,具有before的订阅将在调用相应的实例方法(render)之前触发,而没有before的订阅将在调用相应的实例方法(render)之后触发。还要求在返回false时,具有before的订阅不会执行相应的实例方法和后续订阅。最后,这个设计需求是考虑到在调用组件的实例方法之前,可能会因为一些特殊原因需要取消当前实例方法的调用。例如,如果在调用remove方法时无法删除某些数据,则可以在before订阅中进行一些检查,可以删除的将返回true,而不能删除的将返回false。然后,在实例方法中触发Before订阅后,会添加一个判断,类似于下面的做法:

但是,这种做法只能在简单的回调函数模式下实现,而在发布-订阅模式下不可行,因为回调函数只与一个函数引用相关,而在发布-订阅模式下,同一条消息可能有多个订阅。如果将此实践应用于发布-订阅,当调用this.trigger('beforeRender ')时,与beforeRender关联的所有订阅都将被调用一次。您可能会说队列中最后一个订阅的返回值可以作为标准,在大多数情况下,这样做可能是可以的。但是,当我们把“以队列中最后一个订阅的返回值作为判断标准”的逻辑加入到EventBase中时,就会有很大的风险,那就是在对外使用时,一定要明确管理订阅的顺序。我们必须把与验证等一些特殊逻辑相关的订阅放在后面,但这种与语法和编译无关、需要编码序列的开发模式,会给软件带来相对较大的安全风险。谁能保证订阅顺序在任何时间、任何场景都可以控制?更何况公司里可能有一些新人不知道你写的东西有这样的限制。

解决这个问题的完美方法是,像DOM对象的事件一样,当发布消息时,不是简单地发布消息字符串,而是将消息封装到一个对象中,该对象将被传递给它的所有订阅。如果任何订阅认为消息发布后的逻辑应该被阻塞,只需要调用消息的preventDefault()方法,然后调用消息的isDefaultPrevented()方法,在消息对外发布后进行判断。

这种方法与使用jquery管理DOM对象的事件相同。例如,bootstrap的大多数组件和我在以前的博客中写的组件都使用这种方法来添加额外的判断逻辑。例如,当执行close方法时,bootstrap的警报组件有这样的判断:

按照这个思路,是一种解决问题的方法,但是jquery的一点技巧可以让整个公共对象的EventBase管理变得更加容易。让我们看看它的真面目。

2.jquery特技模式。

1)技能1。

定义组件时,它与一个DOM对象相关联,例如以下形式:

然后我们可以将on off trigger one(常用的事件管理方法)完全添加到这个组件中,然后将这些方法代理到$element的相应方法中:

通过代理,当调用组件的on方法时,实际上调用了$element的on方法,因此这种类型的组件可以支持完美的事件管理。

2)技能2。

第一种技术只能应用于与DOM相关联的组件。我们如何向那些根本不与DOM相关联的组件添加像上一个这样的完美事件管理机制?其实方法也很简单,但是我以前真的没有用过,所以这次用起来会觉得特别新鲜:

查看截图中的框架部分,只要将一个空对象传递给jquery的构造函数,它就会返回一个完美支持事件管理的jquery对象。除了事件管理的功能,它还是一个jquery对象。因此,jquery原型上的所有方法都可以被调用。以后如果需要借用jquery的其他与DOM无关的方法,也可以参考这个小技巧来实现。

3.完美的活动管理。

考虑到第2部分介绍的两种方法中存在重复的逻辑代码,如果结合起来,可以应用到开发组件的所有场景中,可以达到让任何对象都支持本文标题和开篇提到的事件管理功能的目标,所以最后结合前面两种技术,对EventBase进行如下改造(够简单吗):

define(function(require,exports,module){ var $=require(' jquery ');var Class=require(' ./class ');/*** 这个基类可以让普通的类具备框架对象的事件管理能力*/var事件基类=Class({实例成员3360 { init : function(_ jqObject){ this ._ jqObject=_ jqObject _ jqObject实例$ _ jqObject | | $({ });}, function(){ return $。fn。打开。应用(这个._jqObject,参数);},一个:函数(){ return $。fn。一个。应用(这个._jqObject,参数);},off : function(){ return $。fn。关掉。应用(这个._jqObject,参数);},触发:函数(){ return $。fn。扳机。应用(这个._jqObject,参数);}}});返回event base });实际调用测试如下

1)模拟跟数字正射影像图关联的组件

测试代码一:

define(function(require,exports,module){ var $=require(' jquery ');var Class=必选(' mod/Class ');var事件库=必需(' mod/事件库');var Demo窗口。Demo=Class({实例成员3360 { init : }函数(元素,选项){这个.$元素=$(元素);这个,这个$ element);//添加监听this.on('beforeRender ',$ .代理(options.onBeforeRender,this));this.on('render ',$ .代理(options.onRender,this));},render:函数(){//触发脚本事件var e=$ .事件(“BeforeRender”);这个。触发器(e);if(e.isDefaultPrevented())返回;//主要逻辑代码console.log('渲染完成!');//触发提出事件这个。触发器(' render ');}},扩展:事件库});var demo=new Demo('#demo ',{ onbeforerender : function(e){ console。日志('渲染前事件触发!');},onRender:函数{console.log('渲染事件已触发!');}});演示。render();});在这个测试里,我定义了一个跟数字正射影像图关联的演示组件并继承了事件库这个事件管理的类,给脚本事件和提出事件都添加了一个监听,渲染方法中也有打印信息来模拟真实的逻辑,实例化演示的时候用到了#演示这个数字正射影像图元素,最后的测试结果是:

完全与预期一致。

测试代码二:

define(function(require,exports,module){ var $=require(' jquery ');var Class=必选(' mod/Class ');var事件库=必需(' mod/事件库');var Demo窗口。Demo=Class({实例成员3360 { init : }函数(元素,选项){这个.$元素=$(元素);这个,这个$ element);//添加监听this.on('beforeRender ',$ .代理(options.onBeforeRender,this));this.on('render ',$ .代理(options.onRender,this));},render:函数(){//触发脚本事件var e=$ .事件(“BeforeRender”);这个。触发器(e);if(e.isDefaultPrevented())返回;//主要逻辑代码console.log('渲染完成!');//触发提出事件这个。触发器(' render ');}},扩展:事件库});var demo=new Demo('#demo ',{ onbeforerender : function(e){ console。日志('渲染前事件触发!');},onRender:函数{console.log('渲染事件已触发!');}});demo.on('beforeRender ',函数(e){ e . prevent default();console.log('beforeRender事件触发了2!);});demo.on('beforeRender ',函数{console.log('beforeRender事件触发3!');});演示。render();});在这个测试了,我定义了一个跟数字正射影像图相关的演示组件并继承了事件库这个事件管理的类,给脚本事件添加了3个监听,其中一个有加prevetDefault()的调用,而且该回调还不是最后一个,最后的测试结果是:

从结果可以看到,渲染方法的主要逻辑代码跟后面的提出事件都没有执行,所有脚本的监听器都执行了,说明e.preventDefault()生效了,而且它没有对脚本的事件队列产生影响。

2)模拟跟数字正射影像图无关联的普通对象

测试代码一:

define(function(require,exports,module){ var $=require(' jquery ');var Class=required(' mod/Class ');var event base=required(' mod/event base ');var Demo=window . Demo=Class({ instance members 3360 { init : }函数(选项){ this . base();//添加监听这个。on ('beforerender ',$。代理(选项。onbeforerender,this));this.on('render ',$。代理(options.onRender,this));},render:函数(){//触发beforeRender事件var e=$。事件(' BeforeRender ');this . trigger(e);if(e.isDefaultPrevented())返回;//主逻辑代码控制台. log('渲染完成!'。);//触发呈现事件this . trigger(' render ');}},extend : event base });var Demo=new Demo({ onbeforerender :函数(e) {console.log('beforeRender事件已触发!');},onRender:函数(e) {console.log('渲染事件已触发!');}});demo . render();});在这个测试中,我定义了一个与DOM无关的Demo组件,并继承了事件管理类EventBase,并为beforeRender事件和Render事件都添加了一个监视器。渲染方法也有打印信息来模拟真实的逻辑。最终测试结果为:

完全符合预期。

测试代码2:

define(function(require,exports,module){ var $=require(' jquery ');var Class=required(' mod/Class ');var event base=required(' mod/event base ');var Demo=window . Demo=Class({ instance members 3360 { init : }函数(选项){ this . base();//添加监听这个。on ('beforerender ',$。代理(选项。onbeforerender,this));this.on('render ',$。代理(options.onRender,this));},render:函数(){//触发beforeRender事件var e=$。事件(' BeforeRender ');this . trigger(e);if(e.isDefaultPrevented())返回;//主逻辑代码控制台. log('渲染完成!'。);//触发呈现事件this . trigger(' render ');}},extend : event base });var Demo=new Demo({ onbeforerender :函数(e) {console.log('beforeRender事件已触发!');},onRender:函数(e) {console.log('渲染事件已触发!');}});demo.on('beforeRender ',函数(e){ e . preventdefault();console.log('beforeRender事件触发了2!);});demo.on('beforeRender ',函数(e) {console.log('beforeRender事件触发3!');});demo . render();});在这个测试中,我定义了一个与DOM无关的Demo组件,并继承了一个事件管理类EventBase,并为beforeRender事件添加了三个侦听器,其中一个有一个对prevetDefault()的调用,回调不是最后一个。最终测试结果为:

从结果可以看出,render方法的主逻辑代码和后续的render事件都没有执行,beforeRender的所有监听器都已经执行,说明e.preventDefault()已经生效,并没有影响beforeRender的事件队列。

因此,从这两个测试中,通过修改后的EventBase,我们得到了一个可以让任何对象支持jquery事件管理机制的方法。未来在考虑与事件机制解耦时,不需要考虑先介绍的发布-订阅模式。而且这个方法更强大更稳定,更符合你平时使用jquery操作DOM的习惯。

4.本文摘要。

有两点需要再次说明:

1)即使不需要jquery按照第一部分最后提出的思路对第一部分中的常规发布订阅模式进行改革,也只能使用jquery来更加简洁;

2)最后利用jquery的事件机制实现任意对象的事件管理。一方面,使用代理模式,更重要的是,使用发布-订阅模式。然而,最终jquery帮助我们改造了发布-订阅实现的第一部分。

以上内容针对jQuery技术让任何组件都支持类似DOM的事件管理的知识,希望对大家有所帮助!

版权声明:jQuery技术使任何组件都支持类似DOM的事件管理是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。