发布订阅模式的定义
发布订阅模式是一种设计模式,允许对象之间的消息传递,而不需要彼此显示引用,通常用于松耦合的事件处理系统。
发布订阅模式的基本概念和vue的实现分析
- 发布者: 发布者是事件的生产者,它在某个时间发生时通知用户
// vue怎么进行发布?
通过this.$emit, 执行所有订阅该event的事件队列
this.$emit = function(event){
const cbs = _event[event]
cbs.forEach(cb =>{
cb.apply(vm, arguments)
})
}
- 订阅者: 订阅者是事件的消费者,它对某个事件感兴趣,并在事件发生时执行某些操作
// vue怎么进行订阅
// 通过this.$on,注册事件队列,并往事件队列中注册操作
// 在事件管理对象_events上,设置属性,值为一个数组。数组中存放的是订阅者的操作
this.$on = function(event, fn) {
_event[event] || (_event[event] = []).push(fn)
}
- 事件通道: 事件通道是发布者和订阅者的桥梁,负责管理事件的发布和订阅
// 事件通道指什么?
// 事件通道指_event, 是一个普通的对象,通过管理该对象上的属性和属性的值进行管理。
_event: Record<sting, Array<Function>> = Object.create(null)
源码分析
// 初始化事件管理对象
export function initEvents(vm: Component) {
// 事件管理,每个事件名称对应一个监听器数组
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
// 订阅
Vue.prototype.$on = function (
event: string | Array<string>,
fn: Function
): Component {
const vm: Component = this
if (isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
;(vm._events[event] || (vm._events[event] = [])).push(fn)
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}
// 发布
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
if (__DEV__) {
const lowerCaseEvent = event.toLowerCase()
if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
tip(
`Event "${lowerCaseEvent}" is emitted in component ` +
`${formatComponentName(
vm
)} but the handler is registered for "${event}". ` +
`Note that HTML attributes are case-insensitive and you cannot use ` +
`v-on to listen to camelCase events when using in-DOM templates. ` +
`You should probably use "${hyphenate(
event
)}" instead of "${event}".`
)
}
}
let cbs = vm._events[event]
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
const info = `event handler for "${event}"`
for (let i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info)
}
}
return vm
}
// 管理事件通道
Vue.prototype.$once = function (event: string, fn: Function): Component {
const vm: Component = this
function on() {
vm.$off(event, on)
fn.apply(vm, arguments)
}
on.fn = fn
vm.$on(event, on)
return vm
}
Vue.prototype.$off = function (
event?: string | Array<string>,
fn?: Function
): Component {
const vm: Component = this
// all
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// array of events
if (isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$off(event[i], fn)
}
return vm
}
// specific event
const cbs = vm._events[event!]
if (!cbs) {
return vm
}
if (!fn) {
vm._events[event!] = null
return vm
}
// specific handler
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
return vm
}
简单实现一个发布订阅模式
// 定义事件通道类
class EventEmitter {
private events: { [key: string]: Array<(...args: any[]) => void> } = {};
// 订阅事件
on(event: string, listener: (...args: any[]) => void) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
}
// 取消订阅事件
off(event: string, listener: (...args: any[]) => void) {
if (!this.events[event]) return;
this.events[event] = this.events[event].filter(l => l !== listener);
}
// 发布事件
emit(event: string, ...args: any[]) {
if (!this.events[event]) return;
this.events[event].forEach(listener => listener(...args));
}
}
// 创建事件通道实例
const eventEmitter = new EventEmitter();
// 订阅者订阅事件
eventEmitter.on('data', (data) => {
console.log('Data received:', data);
});
// 发布者发布事件
eventEmitter.emit('data', { message: 'Hello, World!' });
// 取消订阅事件
const listener = (data: any) => console.log('Another listener:', data);
eventEmitter.on('data', listener);
eventEmitter.off('data', listener);