概述
在前端里面感觉有很多类库或者模块都用到了这个思想,比如Vue的数据绑定、Vue里面的Bus总线通信、Node.js的Event类(Event类作为一个基类,很多类都继承了他)。
实现一个简单的发布订阅
我们新建一个index.js
,先填入一些方法。首先我们要有两个类,一个是发布者类Pub
,一个是订阅者类Sub
。
订阅者类里面应该保存有订阅者的信息,这里为了简便只保存一个name
,当然少不了的是订阅别人的方法,订阅这个动作是订阅者发出的,所以它应该属于订阅者的类方法。发布者类里应该有一个数组来记录所有的订阅者,然后还要有一个发布方法。
//订阅者
function Sub(name){
this.name = name || '游客'
this.subscribe = function(){
//something
}
}
function Pub(){
this.subcribers = []
this.publish = function(){
//something
}
}
然后我们不急着去补全这些方法,先把我们发布订阅这个逻辑全部走完。
//实例化一个发布者
let peopleNewspaper = new Pub()
//实例化两个订阅者
let zhangsan = new Sub('张三')
let lisi = new Sub('李四')
//订阅操作
zhangsan.subscribe(peopleNewspaper)
lisi.subscribe(peopleNewspaper)
订阅者调用了自身的subscribe
方法去订阅一个发布者,实际上我们要做的是,把这个订阅者添加到发布者的subcribers
数组里,所以我们可以这样写
function Sub(name){
this.subscribe = function(pub){
pub.subscribers.push(this)
}
}
this
指向的就是调用方法的对象,也就是实例zhangsan lisi,但是这样写还有点不太好,订阅者发出订阅动作之后,发布者应该知道是谁订阅了他,所以这边可以这样改一下
function Sub(name){
this.subscribe = function(pub){
pub.add(this)
}
}
function Pub(){
this.add = function(sub){
this.subscribers.push(sub)
//这里只是模拟 通知发布者有新的订阅者订阅,实际上所进行的操作视业务场景而定
console.log(`${sub.name}订阅了你`)
}
}
然后要完成的就是发布代码,直接上代码吧
function Pub() {
this.publish = function (value) {
this.subscribers.forEach(item => {
item.notify(value)
})
}
}
function Sub(name) {
this.notify = function (value) {
console.log(`您好,${this.name}!您有新的报纸--${value}`)
}
}
完整代码如下
//订阅者
function Sub(name) {
this.name = name || '游客'
this.subscribe = function (pub) {
pub.add(this)
}
this.notify = function (value) {
console.log(`您好,${this.name}!您有新的报纸--${value}`)
}
}
//发布者
function Pub() {
this.subscribers = []
//订阅者订阅发布者之后 发布者应该会收到消息
this.add = function (sub) {
this.subscribers.push(sub)
console.log(`${sub.name}订阅了你`)
}
this.publish = function (value) {
this.subscribers.forEach(item => {
item.notify(value)
})
}
}
//实例化一个发布者
let peopleNewspaper = new Pub()
//实例化两个订阅者
let zhangsan = new Sub('张三')
let lisi = new Sub('李四')
//订阅操作
zhangsan.subscribe(peopleNewspaper) //张三订阅了你
lisi.subscribe(peopleNewspaper) //李四订阅了你
//发布者发布新消息
setTimeout(() => {
peopleNewspaper.publish('人民日报') //您好,张三!您有新的报纸--人民日报 您好,李四!您有新的报纸--人民日报
}, 2000)
手写简单的Event类
先用一个简单的例子来说明Node.js里面的EventEmitter是如何使用的
var events = require('events');
var emitter = new events.EventEmitter();
emitter.on('event1', function(arg1, arg2) {
console.log('listener1', arg1, arg2);
});
emitter.on('some_event', function() {
console.log('some_event 事件触发');
});
setTimeout(function() {
emitter.emit('some_event');
}, 1000);
emitter.emit('event1', 'arg1 参数', 'arg2 参数');
//控制台输出
// listener1 arg1 参数 arg2 参数
// some_event 事件触发
这个类包含4个方法,分别是
- on 监听一个事件,并触发回调函数
- emit 发送一个事件
- once 监听一次就移除
- off 移除监听
我们先来实现on
、emit
这两个方法,我们看上述的例子,on
方法第一个参数应该是事件类型,而往后的参数是一些回调函数(可以不止一个回调函数),而emit
方法第一个参数也是事件类型,往后的参数是传给对应的on
方法的回调函数的参数。
//event.js
function Event(){
this.events = []
this.eventsType = [] //存储事件类型 方便后续遍历emit的类型是否已经监听
this.on = function(...event){
//简单的参数校验
if (!event[0]) {
throw new Error('事件类型不可以为空')
}
let _event = {}
_event.type = event[0] //事件类型
_event.fn = event.slice(1) //事件回调函数
this.events.push(_event)
this.eventsType.push(event[0]) //另外存储事件类型,方便后续比较
}
this.emit = function(type,...args){
//简单的参数校验
if (this.eventsType.indexOf(type) === -1) {
throw new Error(`不存在监听${type}的方法`)
}
//遍历events数组,寻找相对应的type
this.events.forEach(item => {
if (item.type === type) {
//调用所有回调函数
item.fn.forEach(fn => {
fn.call(this, ...args)
})
}
})
}
}
接下来来实现off
方法和once
方法,off
方法实现思路比较清晰,直接把对应的事件类型从events
数组里面移除就好了。
this.off = function (type) {
let index = this.eventsType.indexOf(type)
if (index === -1) {
throw new Error(`不存在${type}事件,无法移除`)
} else {
this.eventsType.splice(index, 1)
this.events.forEach((item, index) => {
if (item.type === type) {
this.events.splice(index, 1)
}
})
}
}
测试如下
let event = new Event()
event.on('hello', function (name) {
console.log(`hello,${name}!`)
}, function () {
console.log(1)
})
event.emit('hello', 'David')
event.off('hello')
event.emit('hello', 'David')
//以下为控制台输出
hello,David!
1
event.js:16
throw new Error(`不存在监听${type}的方法`)
^
Error: 不存在监听hello的方法
最后来实现once
方法,实现的思路应该是监听到一次之后就调用off
方法移除,这里要用一个flag表示一个事件是否为once类型,其余逻辑与on
方法差不多,然后emit
方法应该加上一句逻辑,emit
一次之后移除once类型的事件
this.once = function(...event){
let flag = 'once'
if (!event[0]) {
throw new Error('事件类型不可以为空')
}
let _event = {}
_event.type = event[0]
_event.fn = event.slice(1)
_event.flag = flag
this.events.push(_event)
this.eventsType.push(event[0])
}
emit
方法应该加上以下一句
if (item.type === type) {
item.fn.forEach(fn => {
fn.call(this, ...args)
})
//once方法emit一次后应该被移除
if(item.flag === 'once') this.off(item.type)
}
测试如下
let event = new Event()
event.once('hello', function (name) {
console.log(`hello,${name}!`)
}, function () {
console.log(1)
})
event.emit('hello', 'David')
event.emit('hello', 'David')
//以下为控制台输出
hello,David!
1
event.js:16
throw new Error(`不存在监听${type}的方法`)
^
Error: 不存在监听hello的方法
//可以看到确实只监听了一次