事件分布的作用
当向页面添加各种交互功能时,我们知道最简单的方法是将事件绑定到页面元素,然后在事件处理程序中做我们想做的事情。像这样的代码:
element . onclick=function(event){//做任何事情。};如果我们想做的动作不复杂,那么实际的逻辑功能代码可以放在这里。如果将来需要修改,请转到此事件处理程序的位置进行修改。
此外,为了进行适当的代码重用,我们可以将逻辑函数的一部分拆分为一个函数:
element . onclick=function(event){//此处为其他代码。dosomesingelse();};这里的函数doSomethingElse对应的函数可能在其他地方使用,所以会被拆分。此外,可能还有设置坐标的功能(假设函数名为setPosition),还需要浏览器事件对象事件提供的指针位置等信息:
element . onclick=function(event){//此处为其他代码。dosomesingelse();setPosition(event.clientX,event . clienty);};这里不建议将事件对象直接传递给setPosition。这是因为区分逻辑函数和事件拦截是一种很好的做法。只有事件处理程序本身接触到浏览器事件对象事件,有利于减少代码耦合,便于独立测试和维护。
那么,当函数变得越来越复杂时会发生什么呢?如果按照之前的做法,可能是这样的:
element . onclick=function(event){ doMission1();doMission2(event.clientX,event . clienty);多米西3();//.doMissionXX();};虽然这样用没问题,但此时其实可以考虑一个更优雅的写法:
element . onclick=function(event){ amplife . publish(' aya : clicked ',{ x: event.clientX,y : event . clienty });};这种形式就是事件分发。请注意,这里的事件不是指浏览器的本机事件(事件对象),而是逻辑级别的自定义事件。aya :上面点击的是一个随便写的(真的吗?)自定义事件名称。
显然,这还没有结束。为了完成之前的复杂功能,我们还需要将自定义事件与要完成的事情相关联:
amplife . subscribe(' aya : clicked ',doMission1);//.amplife . subscribe(' aya : clicked ',doMission2);//.好像在往回绕?是的,但是它是有用的。一方面,浏览器原生事件的拦截是分离和固化的。如果以后逻辑函数有变化,比如减少几个函数,只需要删除自定义事件的关联代码部分,而不用关注原生事件。另一方面,逻辑功能的调整变得更加灵活,可以通过subscribe在任意代码位置添加功能,分类管理(自定义事件名称)也可以自己完成。
简单来说,事件分发通过增加一层自定义事件的冗余(当只有简单的逻辑功能时,你会认为是冗余的),降低了代码模块之间的耦合度,使得逻辑功能更清晰、更有条理,便于后续维护。
等一下,之前几次离开这个国家的放大器是怎么出现的?
很好,终于到了介绍这个的时候了。放大JS
事件分发需要一定的方法来实现。实现事件分发的设计模式之一是发布/订阅。
AmplifyJS是一个简单的JavaScript库,主要提供三个功能:Ajax请求、数据存储和发布/订阅(每个都可以独立使用)。其中,发布/订阅是核心功能,对应的名称是amplify.core
Amplify.core是发布/订阅设计模式的简洁明了的实现,有100多行评论。在阅读了amplify的源代码之后,我们可以更好地理解如何实现一个发布/订阅的设计模式。代码全景
amplify.core源代码的总体结构如下:
(函数(全局,未定义){var slice=[].切片,订阅={ };var ample=全局。ample={ publish : function(topic){//.},订阅:函数(主题、上下文、回调、优先级){ //.},取消订阅:函数(主题、上下文、回调){ //.}};}(这个));可以看到放大定义了一个名为增强的全局变量(作为全球的的属性),它有3个方法发布、订阅、取消订阅。此外,订阅作为一个局部变量,它将保存发布/订阅模式涉及的所有自定义事件名及其关联函数发布
出版即发布,它要求指定一个话题,也就是自定义事件名(或者就叫做话题),调用后,所有关联到某个主题的函数,都将被依次调用:
出版:函数(主题){ //[1] if(主题类型!=='字符串'){引发新错误('您必须提供有效的主题才能发布。');}//[2]var args=slice。call(参数,1),topicSubscriptions,subscription,length,i=0,retif(!订阅[主题] ) {返回true} //[3]主题订阅=订阅[主题]。slice();for(length=topicsubscriptions。长度;一、长度;I){ subscription=topicSubscriptions[I];ret=订阅。回调。应用(订阅。上下文,参数);if(ret===false){ break;} }返回ret!==false},[1],参数主题必须要求是字符串,否则抛出一个错误。
[2],参数将取得除主题之外的其他所有传递给出版函数的参数,并以数组形式保存。如果对应主题在捐款中没有找到,则直接返回。
[3],主题说明作为一个数组,取得某一个主题下的所有关联元素,其中每一个元素都包括回收及语境两部分。然后,遍历元素,调用每一个关联元素的回调,同时带入元素的语境和前面的额外参数啊。如果任意一个关联元素的回调函数返回假的,则停止运行其他的并返回假的。订阅
订阅,如这个词自己的含义那样(就像订本杂志什么的),是建立主题和回收的关联的步骤。比较特别的是放大在这里还加入了优先级(优先级)的概念,优先级的值越小,优先级越高,默认是10。优先级高的回调,将会在出版的时候,被先调用。这个顺序的原理可以从前面的出版的源码中看到,其实就是预先按照优先级从高到低依次排列好了某一主题的所有关联元素。
订阅:函数(主题、上下文、回调、优先级){ if(主题类型!=='字符串'){引发新错误('您必须提供有效的主题才能创建订阅。');}//[1]if(参数。length===3回调类型===' number '){ priority=callback;回调=上下文;context=null} if(参数。length===2){回调=上下文;上下文=null}优先级=priority | | 10//[2]var topicIndex=0,topics=topic.split(/s/),topicLength=topics.length,已添加;for(;topicIndex topicelengttopicindex){ topic=topics[topicIndex];添加了=falseif(!订阅[主题] ) {订阅[主题]=[];} //[3] var i=订阅[主题]。长度- 1,subscriptionInfo={ callback :回调,上下文:上下文,优先级:优先级};//[4]for(;I=0;i - ) { if(订阅[主题]I .优先级=优先级){订阅[主题]。拼接(i 1,0,订阅信息);添加=真打破;} } //[5] if(!添加了){订阅[主题]。unshift(订阅信息);} }返回回调;},[1],要理解这一部分,请看增强提供的应用程序接口示意:
amplify . subscribe(字符串主题,函数回调)amplife . subscribe(字符串主题,对象上下文,函数回调)amplife . subscribe(字符串主题,函数回调,数字优先级)amplife . subscribe(字符串主题,对象上下文,函数回调,数字优先级)可以看出,amplife允许多种参数形式,当参数的数量和类型不同时,特定位置的参数可能被视为不同的内容。这也可以在许多其他JavaScript库中看到。这样,可以通过判断参数的数量和类型来实现这种多参数设计。
[2],订阅时,topic允许空格,空格字符将被视为分隔符,这意味着回调与多个主题相关联,因此将使用循环。add用作标识符,指示新添加的元素是否已添加到数组中,最初为false。
[3],每个回调的保存实际上是回调之外的一个有上下文(默认为null)和优先级的对象。
[4],这个循环是根据优先级的值找到关联元素应该在的位置。任何主题的关联元素都是从头开始的,按照优先级值从小到大排列(排序)。因此,在比较时,假设新添加元素的优先级值较大(优先级较低),从数组末尾往前比较,只要原数组中关联元素的优先级值小于新添加元素的优先级值,就可以中断循环,通过数组的拼接方法,在这里可以明确添加新添加的元素。如果循环一直运行到结束,可以确定新添加元素的优先级值最小,然后添加会保留初始值false。
[5],如果在这个位置没有添加元素,则可以通过执行添加来确定元素应该在数组的前面(或第一个元素)。取消订阅
虽然发布和订阅是最重要的,但也会有需要退订的时候(如果不想看杂志,就要果断退出!)。因此,我们需要退订。
unsubscribe:函数(主题、上下文、回调){ if (typeof topic!=='字符串'){引发新错误('您必须提供有效的主题才能删除订阅。');} if(arguments . length===2){ callback=context;context=null} if(!订阅[主题]){ return;} var length=订阅[主题]。长度,I=0;for(;一、长度;i ) { if(订阅[主题][ i ]。回调====回调){ if(!上下文||订阅[主题][ i ]。context===context){ subscriptions[topic]。拼接(I,1);//调整已移除项目i -的计数器和长度;长度-;}}}}看完前面的源代码,这部分好像很容易理解。根据指定的主题遍历关联的元素,找到一致的回调,然后删除。因为采用拼接的方式,会直接修改原始数组,所以需要再次手动调整I和长度。放大器的使用示例
官方用法示例之一是:
amplife . subscribe(' data example ',function(data){ alert(data . foo);//bar });//.amplife . publish(' data example ',{ foo : ' bar ' });结合前面的源代码,你对发布/订阅的设计模式有更清晰的理解吗?补充说明
您可能已经注意到,由AmplifyJS实现的典型发布/订阅是同步的。也就是说,当您运行amplifier . publish(topic)时,您将毫不延迟地运行附加到某个主题的所有回调。标签
Pub/Sub是一种易于理解的设计模式,但它非常有用,可以处理大规模应用程序的复杂逻辑。本文简要分析的AmplifyJS是一个JavaScript库,我认为它组织得很好,而且言简意赅(针对单个函数),所以我将在这里与大家分享。