本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
什么是发布-订阅模式
发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。这种模式就像我们在掘金上看到了一篇文章,觉得这篇文章写的很好,对我们有帮助,于是我们还想看以后这个作者的文章,但是我们也不知道作者会在什么时候再次发布,于是我们通常会去关注这个作者,等到这个作者发布了文章后,会有一条消息来通知我们,这时我们就知道已经有新文章可以看了。
发布-订阅模式可以做什么
了解了什么是发布-订阅模式后,我们看看发布-订阅模式能够做什么。在JavaScript设计模式与开发实践中写道,发布-订阅模式可以广泛应用于异步编程,是一种替代传统回调函数的方案。比如,我们可以订阅ajax请求的error,succ等事件,这样我们就无需关注对象在异步运行期间的内部状态,而只需订阅感兴趣的事件发生点。Vue.js的事件系统也是采用的发布-订阅模式实现的,后面我们看看Vue中off,emit的具体实现。
mitt tiny-mitter
下面我们看发布-订阅模式的具体实现,直接上代码
mitt
function mitt(all) {
all = all || new Map()
return {
all: all,
on: function (type, handler) {
var handlers = all.get(type)
if (handlers) {
handlers.push(handler)
} else {
all.set(type, [handler])
}
},
off: function (type, handler) {
var handlers = all.get(type)
if (handlers) {
if (handler) {
// indexOf没找到会返回-1,而对splice来说-1会从数组末尾开始计算
// 所以这里用到了右移运算符 -1>>>0 4294967295,这个数对splice并没有影响
handlers.splice(handlers.indexOf(handler) >>> 0, 1)
} else {
all.set(type, [])
}
}
},
emit: function (type, evt) {
var handlers = all.get(type)
if (handlers) {
handlers.slice().map(function (handler) {
handler(evt)
})
}
handlers = all.get('*')
if (handlers) {
handlers.slice().map(function (handler) {
handler(type, evt)
})
}
}
}
}
tiny-emitter
function E () {
// 为空更容易继承
}
E.prototype = {
on: function (name, callback, ctx) {
var e = this.e || (this.e = {});
(e[name] || (e[name] = [])).push({
fn: callback,
ctx: ctx
});
// 链式调用
return this;
},
once: function (name, callback, ctx) {
var self = this;
function listener () {
self.off(name, listener);
callback.apply(ctx, arguments);
};
listener._ = callback
return this.on(name, listener, ctx);
},
emit: function (name) {
var data = [].slice.call(arguments, 1);
var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
var i = 0;
var len = evtArr.length;
for (i; i < len; i++) {
evtArr[i].fn.apply(evtArr[i].ctx, data);
}
return this;
},
off: function (name, callback) {
var e = this.e || (this.e = {});
var evts = e[name];
var liveEvents = [];
if (evts && callback) {
for (var i = 0, len = evts.length; i < len; i++) {
if (evts[i].fn !== callback && evts[i].fn._ !== callback)
liveEvents.push(evts[i]);
}
}
(liveEvents.length)
? e[name] = liveEvents
: delete e[name];
return this;
}
};
vue中的事件系统
Vue.prototype.$on = function (event, fn) {
const vm = this
// event为数组时,递归调用
if (Array.isArray(event)) {
for (let i = 0; i < event.length; i++) {
this.$on(event[i], fn)
}
} else {
// 添加回调
(vm._events[event] || (vm._events[event] = [])).push(fn)
}
return vm
}
Vue.prototype.$off = function (event, fn) {
const vm = this
// 移除所有事件的监听器
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// event为数组,递归调用$off
if (Array.isArray(event)) {
for (let i = 0; i < event.length; i++) {
this.$off(event[i], fn)
}
return vm
}
const cbs = vm._events[event]
if (!cbs) {
return vm
}
// 只提供事件名,移除该事件的所有监听器
if (arguments.length === 1) {
vm._events[event] = null
return vm
}
// 提供了事件与回调,只移除这个回调的监听
if (fn) {
const cbs = vm._events[event]
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
}
return vm
}
// 监听一个自定义事件,只触发一次,第一次触发后移除监听器
Vue.prototype.$once = function (event, fn) {
const vm = this
function on() {
vm.$off(event, on)
fn.apply(vm, arguments)
}
on.fn = fn
vm.$on(event, on)
return vm
}
Vue.prototype.$emit = function (event) {
const vm = this
let cbs = vm._events[event]
if (cbs) {
const args = toArray(arguments, 1)
for (let i = 0; i < cbs.length; i++) {
try {
cbs[i].apply(vm, args)
} catch (error) {
handleError()
}
}
}
return vm
}
总结
- 什么是发布-订阅模式
- 发布-订阅模式能做什么
- 如何实现一个发布-订阅模式
- 无符号右移操作符的使用
- 删除数组中某一项的不同操作之间的区别