EventBus的实现

EventBus概要EventBus是消息传递的一种方式,基于一个消息中心,订阅和发布消息的模式。这种方式的实现不仅仅局限于前端,在iOS中的消息消息中心也是如此实现。设计模式:订阅者发布者模式,这种设计模式在前端很常见。API的设计:2.1 只能构造一个消息对象2.2 on(‘msgName’, func)订阅消息,msgName:订阅的消息名称 func: 订阅的消息2.3 one(‘msgName’, func)仅订阅一次消息,后订阅的会替换前面订阅的消息2.4 emit(‘msgName’, msg)发布消息 msgName:消息名称 msg:发布的消息2.5 off(‘msgName’)移除消息实现EventBus// 构造EventBusfunction EventBusClass() { this.msgQueues = {}}EventBusClass.prototype = { // 将消息保存到当前的消息队列中 on: function(msgName, func) { if (this.msgQueues.hasOwnProperty(msgName)) { if (typeof this.msgQueues === ‘function’) { this.msgQueues[msgName] = [this.msgQueues[msgName], func] } else { this.msgQueues[msgName] = […this.msgQueues[msgName], func] } } else { this.msgQueues[msgName] = func; } }, // 消息队列中仅保存一个消息 one: function(msgName, func) { // 无需检查msgName是否存在 this.msgQueues[msgName] = func; }, // 发送消息 emit: function(msgName, msg) { if (!this.msgQueues.hasOwnProperty(msgName)) { return } if (typeof this.msgQueues[msgName] === ‘function’) { this.msgQueuesmsgName } else { this.msgQueues[msgName].map((fn) => { fn(msg) }) } }, // 移除消息 off: function(msgName) { if (!this.msgQueues.hasOwnProperty(msgName)) { return } delete this.msgQueues[msgName] }}// 将EventBus放到window对象中const EventBus = new EventBusClass()window.EventBus = EventBus使用EventBus// 订阅消息function subscribe() { EventBus.on(‘first-event’, function(msg) { alert(订阅的消息是:${msg}); });}// 发送消息function emit() { const msgInput = document.getElementById(“msgInputId”) EventBus.emit(‘first-event’, msgInput.value)}// 移除消息function off(msgName) { EventBus.off(msgName)}CodePen预览CodePen预览—–可能需要翻墙—–<p data-height=“265” data-theme-id=“0” data-slug-hash=“maQpgR” data-default-tab=“js,result” data-user=“beyondverage0908” data-pen-title=“EventBus的实现” style=“height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid black; margin: 1em 0; padding: 1em;” class=“codepen”><span>See the Pen EventBus的实现 by avg (@beyondverage0908) on CodePen.</span></p><script async src=“https://static.codepen.io/ass...;></script>总结整个EventBus主要部分是分为三个部分。消息中心,订阅事件方法,发布消息方法。虽然不能和Vue中实现的那么全面,但麻雀虽小,五脏俱全。缺少的部分在于对数据安全性的校验。希望给你一个实现的思路 ...

January 16, 2019 · 1 min · jiezi

vue -- 非父子组件传值,事件总线(eventbus)的使用方式

欢迎访问我的个人博客:http://www.xiaolongwu.cn前言先说一下什么是事件总线,其实就是订阅发布者模式;比如有一个bus对象,这个对象上有两个方法,一个是on(监听,也就是订阅),一个是emit(触发,也就是发布),我们通过on方法去监听某个事件,再用emit去触发这个事件,同时调用on中的回调函数,这样就完成了一次事件触发;这是一种设计模式,和语言没有关系;如果不太了解什么是订阅发布者模式,请移步看这篇文章JavaScript设计模式–观察者模式(发布者-订阅者模式)在实际开发中,往往最麻烦的就是各种组件之间的传值问题;如果利用事件总线就会让这件事情变得很简单;vue自带事件总线的短板我们都知道在vue被实例化之后,他就具备了充当事件总线对象的能力,在他上面挂了两个方法,是$emit和$on;而vue文档说的很明白,$emit会触发当前实例上的事件,附加参数都会传给监听器回调;由于在实际工作中,我们都是以组件的形式开发,每个组件就是一个实例;所以利用vue自带的总线能力有很大的局限性,最多只能从子组件触发到父组件中,而不能在非父子组件之间传值;所以这时,我们就需要有一个全局的事件总线对象,让我们挂载监听事件和触发事件;举个例子,子组件向父组件传值;父组件向子组件传值很简单,我们这里不说// 子组件中<template> <div> <span>{{child}}</span> <input type=“button” value=“点击触发” @click=“send”> </div></template><script> export default { data () { return { child: ‘我是子组件的数据’ } }, methods: { send () { // 如果传多个值就用逗号隔开 a, b, c this.$emit(‘fromChild’, this.child) } } }</script>// 父组件<template> <div> <span>{{name}}</span> // 在父组件中监听 fromChild事件 <child @fromChild=“onFromChild”></child> </div></template><script> import child from ‘./child’ export default { components: { child }, data () { return { name: ’’ } }, methods: { onFromChild: function (data) { // data就是子组件传过来的值 // 如果传过来多个值就用逗号隔开去接收 data1, data2, data3 this.name = data } } }</script>实现全局事件总线对象的几种方式方式一,也是我自己使用的方式(推荐使用,简单)大概思路是 :在main.js,也就是入口文件中,我们在vue的原型上添加一个bus对象;具体实现方式如下:下面的组件A和组件B可以是项目中任意两个组件//在mian.js中Vue.prototype.bus = new Vue() //这样我们就实现了全局的事件总线对象//组件A中,监听事件this.bus.$on(‘updata’, function(data) { console.log(data) //data就是触发updata事件带过来的数据})//组件B中,触发事件this.bus.$emit(‘updata’, data) //data就是触发updata事件要带走的数据方式二,稍微有点麻烦,但也很容易理解大概的实现思路: 新建一个bus.js文件, 在这个文件里实例化一下vue;然后在组件A和组件B中分别引入这个bus.js文件,将事件监听和事件触发都挂到bus.js这个实例上,这样就可以实现全局的监听与触发了写个例子bus.js文件// bus.js文件import Vue from ‘vue’export default new Vue()组件A// 组件A ,监听事件send<template> <div> <span>{{name}}</span> </div></template><script> import Bus from ‘./bus.js’ export default { data () { return { name: ’’ } }, created() { let _this = this // 用$on监听事件并接受数据 Bus.$on(‘send’, (data) => { _this.name = data console.log(data) }) }, methods: {} }</script>组件B// 组件B, 触发事件send<template> <div> <input type=“button” value=“点击触发” @click=“onClick”> </div></template><script> import Bus from ‘./bus.js’ export default { data () { return { elValue: ‘我是B组件数据’ } }, methods: { // 发送数据 onClick() { Bus.$emit(‘send’, this.elValue) } } }</script>这样我们就完成了一个简单非父子组件之间的传值。我的个人博客地址:http://www.xiaolongwu.cngithub资源地址:vue – 非父子组件传值,事件总线(eventbus)的使用方式我的CSDN博客地址:https://blog.csdn.net/wxl1555如果您对我的博客内容有疑惑或质疑的地方,请在下方评论区留言,或邮件给我,共同学习进步。邮箱:wuxiaolong802@163.com ...

January 10, 2019 · 1 min · jiezi

猫头鹰的深夜翻译:设计模式EventBus

前言今天,我们将介绍一个比较新的设计模式(也就是没有在GoF那本书中出现过的一种设计模式),这个设计模式就是Event Bus设计模式。起源假设一个大型应用中,有大量的组件彼此间存在交互。而你希望能够在组件通信的同时能够满足低耦合和关注点分离原则。Event Bus设计模式是一个很好的解决方案。Event Bus的概念和网络中的总线拓扑概念类似。即存在某种管道,而所有的电脑都连接在这条管道之上。其中的任何一台电脑发送的消息都将分发给总线上所有其它的主机。然后,每台主机决定是否接收还是抛弃掉这条消息。在组件的层面上也是类似的:主机对应着应用的组件,消息对应于事件(event)或者数据。而管道是Event Bus对象。实现并没有一种绝对正确的实现EventBus的方式。在这里我会简单的介绍两种方法。第一种方法这是比较经典的一种方法,它主要取决于定义EventBus接口(从而强制实现一个特定的协议),按需对其实现,然后定义一个Subscriber(另一份协议)来进行Event的处理。/** * interface describing a generic event, and it’s associated meta data, it’s this what’s going to * get sent in the bus to be dispatched to intrested Subscribers * * @author chermehdi /public interface Event<T> { /* * @returns the stored data associated with the event / T getData();}import java.util.Set;/* * Description of a generic subscriber * * @author chermehdi /public interface Subscribable { /* * Consume the events dispatched by the bus, events passed as parameter are can only be of type * declared by the supports() Set / void handle(Event<?> event); /* * describes the set of classes the subscribable object intends to handle / Set<Class<?>> supports();}import java.util.List;/* * Description of the contract of a generic EventBus implementation, the library contains two main * version, Sync and Async event bus implementations, if you want to provide your own implementation * and stay compliant with the components of the library just implement this contract * * @author chermehdi /public interface EventBus { /* * registers a new subscribable to this EventBus instance / void register(Subscribable subscribable); /* * send the given event in this EventBus implementation to be consumed by interested subscribers / void dispatch(Event<?> event); /* * get the list of all the subscribers associated with this EventBus instance / List<Subscribable> getSubscribers();}Subscribable接口定义了一个方法来处理一个特定类型的消息,并且通过定义supports方法决定支持哪种类型。EventBus的实现持有所有Subscribable对象,并且每当一个新事件触发dispatch方法时,通知所有的Subscribable对象。这种方案的有点事可以在编译时检查传递过来的Subscribable对象,而且更加符合面向对象的思想,因为无需使用反射。同时,可以看到,这种方案更容易实现。缺点是接口的强制性–你总是需要一个新的类来处理一种类型的Event,在项目初期这个问题可能不明显,但是随着项目发展,你会发现,新建一个类只是为了处理简单的逻辑比如日志或是数据分析会显得很冗余。第二种方法这个方法来源于Guava的实现。EventBus看上去更简单更好用,对于每个时间的consumer, 你只需要通过对一个方法加上@Subscribe注解,并且在注解的参数中传入你希望处理的对象类型(单个对象/参数)。然后你通过调用eventBus.register(objectContainingTheMethod)来注册事件的消费者。要产生一个新的时间,你只需要调用eventBus.post(someObject),然后所有相关的消费者都将会被通知。如果对应一个特定的对象没有对应的消费者怎么办?在guava的实现中,它们被称为DeadEvents,在我的实现中,post调用会被忽略。import java.lang.reflect.Method;import java.util.Arrays;import java.util.List;import java.util.Map;import java.util.Vector;import java.util.concurrent.ConcurrentHashMap;import java.util.stream.Collectors;/* * Simple implementation demonstrating how a guava EventBus works generally, without all the noise * of special cases handling, and special guava collections * * @author chermehdi */public class EventBus { private Map<Class<?>, List<Invocation>> invocations; private String name; public EventBus(String name) { this.name = name; invocations = new ConcurrentHashMap<>(); } public void post(Object object) { Class<?> clazz = object.getClass(); if (invocations.containsKey(clazz)) { invocations.get(clazz).forEach(invocation -> invocation.invoke(object)); } } public void register(Object object) { Class<?> currentClass = object.getClass(); // we try to navigate the object tree back to object ot see if // there is any annotated @Subscribe classes while (currentClass != null) { List<Method> subscribeMethods = findSubscriptionMethods(currentClass); for (Method method : subscribeMethods) { // we know for sure that it has only one parameter Class<?> type = method.getParameterTypes()[0]; if (invocations.containsKey(type)) { invocations.get(type).add(new Invocation(method, object)); } else { List<Invocation> temp = new Vector<>(); temp.add(new Invocation(method, object)); invocations.put(type, temp); } } currentClass = currentClass.getSuperclass(); } } private List<Method> findSubscriptionMethods(Class<?> type) { List<Method> subscribeMethods = Arrays.stream(type.getDeclaredMethods()) .filter(method -> method.isAnnotationPresent(Subscribe.class)) .collect(Collectors.toList()); checkSubscriberMethods(subscribeMethods); return subscribeMethods; } private void checkSubscriberMethods(List<Method> subscribeMethods) { boolean hasMoreThanOneParameter = subscribeMethods.stream() .anyMatch(method -> method.getParameterCount() != 1); if (hasMoreThanOneParameter) { throw new IllegalArgumentException( “Method annotated with @Susbscribe has more than one parameter”); } } public Map<Class<?>, List<Invocation>> getInvocations() { return invocations; } public String getName() { return name; }}可以看到这种方案所需要的额外工作比较少。你只需要定义方法的名称而不是为各个处理器命名。而且你可以将所有的消费者定义在一个类中。你只需要为每个方法传递不同的事件类型即可。 ...

November 12, 2018 · 3 min · jiezi

vue篇之事件总线(EventBus)

许多现代JavaScript框架和库的核心概念是能够将数据和UI封装在模块化、可重用的组件中。这对于开发人员可以在开发整个应用程序时避免使用编写大量重复的代码。虽然这样做非常有用,但也涉及到组件之间的数据通讯。在Vue中同样有这样的概念存在。通过前面一段时间的学习,Vue组件数据通讯常常会有父子组件,兄弟组件之间的数据通讯。也就是说在Vue中组件通讯有一定的原则。父子组件通讯原则为了提高组件的独立性与重用性,父组件会通过 props 向下传数据给子组件,当子组件有事情要告诉父组件时会通过 $emit 事件告诉父组件。如此确保每个组件都是独立在相对隔离的环境中运行,可以大幅提高组件的维护性。在《Vue组件通讯》一文中有详细介绍过这部分。但这套通讯原则对于兄弟组件之间的数据通讯就有一定的诟病。当然,在Vue中有其他的方式来处理兄弟组件之间的数据通讯,比如Vuex这样的库。但在很多情况之下,咱们的应用程序不需要类似Vuex这样的库来处理组件之间的数据通讯,而可以考虑Vue中的 事件总线 ,即 EventBus 。接下来的内容,就是来一起学习Vue中的 EventBus 相关的知识点。EventBus的简介EventBus 又称为事件总线。在Vue中可以使用 EventBus 来作为沟通桥梁的概念,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以上下平行地通知其他组件,但也就是太方便所以若使用不慎,就会造成难以维护的灾难,因此才需要更完善的Vuex作为状态管理中心,将通知的概念上升到共享状态层次。如何使用EventBus在Vue的项目中怎么使用 EventBus 来实现组件之间的数据通讯呢?具体可以通过下面几个步骤来完成。初始化首先你需要做的是创建事件总线并将其导出,以便其它模块可以使用或者监听它。我们可以通过两种方式来处理。先来看第一种,新创建一个 .js 文件,比如 event-bus.js :// event-bus.jsimport Vue from ‘vue’export const EventBus = new Vue()你需要做的只是引入 Vue 并导出它的一个实例(在这种情况下,我称它为 EventBus )。实质上它是一个不具备 DOM 的组件,它具有的仅仅只是它实例方法而已,因此它非常的轻便。另外一种方式,可以直接在项目中的 main.js 初始化 EventBus :// main.jsVue.prototype.$EventBus = new Vue()注意,这种方式初始化的 EventBus 是一个 全局的事件总线 。稍后我们会花点时间专门聊一聊全局的事件总线。现在我们已经创建了 EventBus ,接下来你需要做到的就是在你的组件中加载它,并且调用同一个方法,就如你在父子组件中互相传递消息一样。发送事件假设你有两个子组件: DecreaseCount 和 IncrementCount ,分别在按钮中绑定了 decrease()和 increment() 方法。这两个方法做的事情很简单,就是数值递减(增) 1 ,以及角度值递减(增) 180 。在这两个方法中,通过 EventBus.$emit(channel: string, callback(payload1,…)) 监听 decreased (和 incremented )频道。<!– DecreaseCount.vue –><template> <button @click=“decrease()">-</button></template><script> import { EventBus } from “../event-bus.js”; export default { name: “DecreaseCount”, data() { return { num: 1, deg:180 }; }, methods: { decrease() { EventBus.$emit(“decreased”, { num:this.num, deg:this.deg }); } } }; </script><!– IncrementCount.vue –><template> <button @click=“increment()">+</button></template><script> import { EventBus } from “../event-bus.js”; export default { name: “IncrementCount”, data() { return { num: 1, deg:180 }; }, methods: { increment() { EventBus.$emit(“incremented”, { num:this.num, deg:this.deg }); } } }; </script>上面的示例,在 DecreaseCount 和 IncrementCount 分别发送出了 decreased 和 incremented频道。接下来,我们需要在另一个组件中接收这两个事件,保持数据在各组件之间的通讯。接收事件现在我们可以在组件 App.vue 中使用 EventBus.$on(channel: string, callback(payload1,…))监听 DecreaseCount 和 IncrementCount 分别发送出了 decreased 和 incremented 频道。<!– App.vue –><template> <div id=“app”> <div class=“container” :style="{transform: ‘rotateY(’ + degValue + ‘deg)’}"> <div class=“front”> <div class=“increment”> <IncrementCount /> </div> <div class=“show-front”> {{fontCount}} </div> <div class=“decrement”> <DecreaseCount /> </div> </div> <div class=“back”> <div class=“increment”> <IncrementCount /> </div> <div class=“show-back”> {{backCount}} </div> <div class=“decrement”> <DecreaseCount /> </div> </div> </div> </div></template><script> import IncrementCount from “./components/IncrementCount”; import DecreaseCount from “./components/DecreaseCount”; import { EventBus } from “./event-bus.js”; export default { name: “App”, components: { IncrementCount, DecreaseCount }, data() { return { degValue:0, fontCount:0, backCount:0 }; }, mounted() { EventBus.$on(“incremented”, ({num,deg}) => { this.fontCount += num this.$nextTick(()=>{ this.backCount += num this.degValue += deg; }) }); EventBus.$on(“decreased”, ({num,deg}) => { this.fontCount -= num this.$nextTick(()=>{ this.backCount -= num this.degValue -= deg; }) }); } }; </script>最终得到的效果如下:最后用一张图来描述示例中用到的 EventBus 之间的关系:如果你只想监听一次事件的发生,可以使用 EventBus.$once(channel: string, callback(payload1,…)) 。移除事件监听者如果想移除事件的监听,可以像下面这样操作:import { eventBus } from ‘./event-bus.js’EventBus.$off(‘decreased’, {})你也可以使用 EventBus.$off(‘decreased’) 来移除应用内所有对此事件的监听。或者直接调用EventBus.$off() 来移除所有事件频道, 注意不需要添加任何参数 。上面就是 EventBus 的使用方式,是不是很简单。上面的示例中我们也看到了,每次使用 EventBus 时都需要在各组件中引入 event-bus.js 。事实上,我们还可以通过别的方式,让事情变得简单一些。那就是创建一个全局的 EventBus 。接下来的示例向大家演示如何在Vue项目中创建一个全局的 EventBus 。全局EventBus全局EventBus,虽然在某些示例中不提倡使用,但它是一种非常漂亮且简单的方法,可以跨组件之间共享数据。它的工作原理是发布/订阅方法,通常称为 Pub/Sub 。这整个方法可以看作是一种设计模式,因为如果你查看它周围的东西,你会发现它更像是一种体系结构解决方案。我们将使用普通的JavaScript,并创建两个组件,并演示EventBus的工作方式。让我们看看下图,并试着了解在这种情况下究竟发生了什么。我们从上图中可以得出以下几点:有一个全局EventBus所有事件都订阅它所有组件也发布到它,订阅组件获得更新总结一下。所有组件都能够将事件发布到总线,然后总线由另一个组件订阅,然后订阅它的组件将得到更新在代码中,我们将保持它非常小巧和简洁。我们将它分为两部分,将展示两个组件以及生成事件总线的代码。创建全局EventBus全局事件总线只不过是一个简单的 vue 组件。代码如下:var EventBus = new Vue();Object.defineProperties(Vue.prototype, { $bus: { get: function () { return EventBus } }})现在,这个特定的总线使用两个方法 $on 和 $emit 。一个用于创建发出的事件,它就是$emit ;另一个用于订阅 $on :var EventBus = new Vue();this.$bus.$emit(’nameOfEvent’,{ … pass some event data …});this.$bus.$on(’nameOfEvent’,($event) => { // …})现在,我们创建两个简单的组件,以便最终得出结论。接下来的这个示例中,我们创建了一个 ShowMessage 的组件用来显示信息,另外创建一个 UpdateMessage 的组件,用来更新信息。在 UpdateMessage 组件中触发需要的事件。在这个示例中,将触发一个 updateMessage 事件,这个事件发送了 updateMessage 的频道:<!– UpdateMessage.vue –><template> <div class=“form”> <div class=“form-control”> <input v-model=“message” > <button @click=“updateMessage()">更新消息</button> </div> </div></template><script>export default { name: “UpdateMessage”, data() { return { message: “这是一条消息” }; }, methods: { updateMessage() { this.$bus.$emit(“updateMessage”, this.message); } }, beforeDestroy () { $this.$bus.$off(‘updateMessage’) } }; </script>同时在 ShowMessage 组件中监听该事件:<!– ShowMessage.vue –><template> <div class=“message”> <h1>{{ message }}</h1> </div></template><script> export default { name: “ShowMessage”, data() { return { message: “我是一条消息” }; }, created() { var self = this this.$bus.$on(‘updateMessage’, function(value) { self.updateMessage(value); }) }, methods: { updateMessage(value) { this.message = value } } }; </script><最终的效果如下:从上面的代码中,我们可以看到 ShowMessage 组件侦听一个名为 updateMessage 的特定事件,这个事件在组件实例化时被触发,或者你可以在创建组件时触发。另一方面,我们有另一个组件UpdateMessage ,它有一个按钮,当有人点击它时会发出一个事件。这导致订阅组件侦听发出的事件。这产生了 Pub/Sub 模型,该模型在兄弟姐妹之间持续存在并且非常容易实现。总结本文主要通过两个实例学习了Vue中有关于 EventBus 相关的知识点。主要涉及了 EventBus 如何实例化,又是怎么通过 $emit 发送频道信号,又是如何通过 $on 来接收频道信号。最后简单介绍了怎么创建全局的 EventBus 。从实例中我们可以了解到, EventBus 可以较好的实现兄弟组件之间的数据通讯。 ...

October 11, 2018 · 3 min · jiezi