介绍
mitt 是一个小而美的公布-订阅库,短短的几十行代码,小于 200b 的体积,提供三个重要的 API。然而麻雀虽小,五脏俱全。
公布-订阅模式
公布-订阅模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在本身状态变动时,会告诉所有订阅者对象,使它们可能自动更新本人的状态。
用法
有这样的一个需要,小明想买 100w 以内的二手房,小华想买 150w 左右的二手房,然而房产中介通知他俩,临时没有他们想要的房源,中介的工作人员留下他俩的联系方式,一旦有适合的房源就通过电话告诉。这就是一个经典应用公布-订阅的模式的场景。请看上面的代码。
const mitt = require("mitt");const houseAgents = mitt();houseAgents.on("xiaoming", () => { console.log("有 100w 以内的房源了");});houseAgents.on("xiaohua", () => { console.log("有 150w 左右的房源了");});
过了一段时间,中介收到 150w 左右的房源,立马告诉 xiaohua。
houseAgents.emit("xiaohua");
源码剖析
mitt 通过几十行代码实现了公布-订阅机制。咱们来分析一下 mitt 源码。
function mitt(all) { all = all || Object.create(null); return { on: function () { /* some code*/ }, emit: function () { /* some code*/ }, off: function () { /* some code*/ }, };}
mitt 库的源码中只有一个 mitt 函数。它接管 all 参数,返回一个对象,该对象蕴含 on、emit、off 三个办法。
all = all || Object.create(null);
mitt 办法接管 all 参数,all 用来存储监听的事件。当传入的 all 的值是 undefined
、null
、""
、false
、NaN
等值时,all 的值默认为 Object.create(null)
, 它是一个没有 __proto__
属性的对象。
实际上,这里短少类型解决。比方 mitt(true), 就会导致谬误。
const mitt = require("mitt");const ob = mitt(true);ob.on("a", () => {});ob.emit("a", "dd");
倡议 all 的类型是对象类型或者不传。举荐传入空对象或数组。
const mitt = require("mitt");const ob = mitt();/* or const ob = mitt([]); const ob = mitt({});*/
接下来,咱们来看看 mitt 里十分重要的三个办法。
// ...on: function on(type, handler) { (all[type] || (all[type] = [])).push(handler);}// ...
on 接管两个参数,type 类型和 handler 事件处理办法。办法体内仅有一行十分优雅的代码。当该类型存在时,就将其事件处理追加到数组前面,当该类型不存在时,初始化一个空数组,用来存储该类型的事件处理办法。
emit: function emit(type, evt) { (all[type] || []).slice().map(function (handler) { handler(evt); }); (all["*"] || []).slice().map(function (handler) { handler(type, evt); });}
订阅事件应用 on 办法,公布事件应用 emit 办法。emit 办法里就做了一件事,依据事件类型,将该类型订阅的所有事件遍历调用。
有余
通过剖析 mitt 的源码,咱们会发现,mitt 没有思考匿名函数状况,在应用 on 办法时,传入的第二个参数必须是具名函数。
手动实现公布订阅库
class PubSub { constructor() { this.listeners = []; } sub(type, handler, always) { console.log(this.listeners[type] || []); if (!this.listeners[type]) { this.listeners[type] = []; } this.listeners[type].push({ handler, always }); } on(type, handler, always = true) { this.sub(type, handler, always); } once(type, handler, always = false) { this.sub(type, handler, always); } emit(type, evt) { if (this.listeners[type]) { this.listeners[type].forEach((listener) => { listener.handler(evt); }); this.listeners[type] = this.listeners[type].slice().filter((listener) => Boolean(listener.always)); } } off(type, handler) { if (this.listeners[type]) { this.listeners[type] = this.listeners[type] .slice() .filter((listener) => listener.handler.toString() !== handler.toString()); } }}