# mitt、tiny-emitter 发布订阅
发布订阅模式:当使用者订阅了某一个对象,当对象发生对应的动作之后,会通知到使用者事先设定的函数。
在前端中举个简单的例子,对按钮点击事件的监听。当按钮被点击之后,某一个事先设定的函数会被调用,借助的是 addEventListener
# 环境准备
克隆两个仓库
git clone https://github.com/developit/mitt.git
git clone https://github.com/scottcorgan/tiny-emitter.git
# tiny-emitter
克隆github项目文件,把并进入项目文件项目文件克隆下来,并查看 项目的 package.json
文件
# package.json
"scripts": {
"test-node": "tape test/index.js | tap-format-spec",
"test": "testling | tap-format-spec",
"bundle": "node_modules/.bin/browserify index.js > dist/tinyemitter.js -s TinyEmitter && echo 'Bundled'",
"minify": "node_modules/.bin/uglifyjs dist/tinyemitter.js -o dist/tinyemitter.min.js -m && echo 'Minified'",
"build": "npm test && npm run bundle && npm run minify",
"size": "node_modules/.bin/uglifyjs index.js -o minified.js -m && ls -l && rm minified.js"
},
"testling": {
"files": [
"test/index.js"
],
# testling
简单的用法就是,在packages.json添加一个testling的字段,然后再命令行中testling会自动将files字段里面的文件进行测试。
详情使用请参考:npm包地址: https://www.npmjs.com/package/testling (opens new window)
源码,点击查看更多
function E () {
// Keep this empty so it's easier to inherit from
// (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
}
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]);
}
}
// Remove event from queue to prevent memory leak
// Suggested by https://github.com/lazd
// Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910
(liveEvents.length)
? e[name] = liveEvents
: delete e[name];
return this;
}
};
module.exports = E;
module.exports.TinyEmitter = E;
编写了一个函数,在函数的原型链上提供了四个方法
- on: 监听一个事件
- once:一次性监听一个事件
- emit: 触发事件
- off: 取消事件的监听
# 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) {
// 丢掉第0个参数的入参浅拷贝
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++) {
// 将参数 使用apply进行调用
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 = [];
// 如果存在 evts 和 回调函数
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]);
}
}
// Remove event from queue to prevent memory leak
// Suggested by https://github.com/lazd
// Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910
// 赋值
(liveEvents.length)
? e[name] = liveEvents
: delete e[name];
return this;
}
# mitt
mitt包是使用TS语言编写的,如果需要详细了解的,ts中文官网 (opens new window)
源码
export type EventType = string | symbol;
// An event handler can take an optional event argument
// and should not return a value
export type Handler<T = unknown> = (event: T) => void;
export type WildcardHandler<T = Record<string, unknown>> = (
type: keyof T,
event: T[keyof T]
) => void;
// An array of all currently registered event handlers for a type
export type EventHandlerList<T = unknown> = Array<Handler<T>>;
export type WildCardEventHandlerList<T = Record<string, unknown>> = Array<WildcardHandler<T>>;
// A map of event types and their corresponding event handlers.
export type EventHandlerMap<Events extends Record<EventType, unknown>> = Map<
keyof Events | '*',
EventHandlerList<Events[keyof Events]> | WildCardEventHandlerList<Events>
>;
export interface Emitter<Events extends Record<EventType, unknown>> {
all: EventHandlerMap<Events>;
on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): void;
on(type: '*', handler: WildcardHandler<Events>): void;
off<Key extends keyof Events>(type: Key, handler?: Handler<Events[Key]>): void;
off(type: '*', handler: WildcardHandler<Events>): void;
emit<Key extends keyof Events>(type: Key, event: Events[Key]): void;
emit<Key extends keyof Events>(type: undefined extends Events[Key] ? Key : never): void;
}
/**
* Mitt: Tiny (~200b) functional event emitter / pubsub.
* @name mitt
* @returns {Mitt}
*/
export default function mitt<Events extends Record<EventType, unknown>>(
all?: EventHandlerMap<Events>
): Emitter<Events> {
type GenericEventHandler =
| Handler<Events[keyof Events]>
| WildcardHandler<Events>;
all = all || new Map();
return {
/**
* A Map of event names to registered handler functions.
*/
all,
/**
* Register an event handler for the given type.
* @param {string|symbol} type Type of event to listen for, or `'*'` for all events
* @param {Function} handler Function to call in response to given event
* @memberOf mitt
*/
on<Key extends keyof Events>(type: Key, handler: GenericEventHandler) {
const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
if (handlers) {
handlers.push(handler);
}
else {
all!.set(type, [handler] as EventHandlerList<Events[keyof Events]>);
}
},
/**
* Remove an event handler for the given type.
* If `handler` is omitted, all handlers of the given type are removed.
* @param {string|symbol} type Type of event to unregister `handler` from, or `'*'`
* @param {Function} [handler] Handler function to remove
* @memberOf mitt
*/
off<Key extends keyof Events>(type: Key, handler?: GenericEventHandler) {
const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
if (handlers) {
if (handler) {
handlers.splice(handlers.indexOf(handler) >>> 0, 1);
}
else {
all!.set(type, []);
}
}
},
/**
* Invoke all handlers for the given type.
* If present, `'*'` handlers are invoked after type-matched handlers.
*
* Note: Manually firing '*' handlers is not supported.
*
* @param {string|symbol} type The event type to invoke
* @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler
* @memberOf mitt
*/
emit<Key extends keyof Events>(type: Key, evt?: Events[Key]) {
let handlers = all!.get(type);
if (handlers) {
(handlers as EventHandlerList<Events[keyof Events]>)
.slice()
.map((handler) => {
handler(evt!);
});
}
handlers = all!.get('*');
if (handlers) {
(handlers as WildCardEventHandlerList<Events>)
.slice()
.map((handler) => {
handler(type, evt!);
});
}
}
};
}
mitt函数返回了一个函数,其中包含了all,on,off,emit四个属性
# on
on<Key extends keyof Events>(type: Key, handler: GenericEventHandler) {
// 如果存在 事件类型 则直接压入,如果不存在则新建
const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
if (handlers) {
handlers.push(handler);
}
else {
all!.set(type, [handler] as EventHandlerList<Events[keyof Events]>);
}
},
# off
off<Key extends keyof Events>(type: Key, handler?: GenericEventHandler) {
const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
if (handlers) {
if (handler) {
// 使用splice 从事件集数组中删掉
handlers.splice(handlers.indexOf(handler) >>> 0, 1);
}
else {
// handler 不存在 则删掉所有的监听事件
all!.set(type, []);
}
}
},
# emit
emit<Key extends keyof Events>(type: Key, evt?: Events[Key]) {
let handlers = all!.get(type);
if (handlers) {
// 浅拷贝之后进行遍历执行
(handlers as EventHandlerList<Events[keyof Events]>)
.slice()
.map((handler) => {
handler(evt!);
});
}
handlers = all!.get('*');
if (handlers) {
(handlers as WildCardEventHandlerList<Events>)
.slice()
.map((handler) => {
handler(type, evt!);
});
}
}
# 收获
- 两个项目的对比,一个js和一个TS,被动中增加了对TS的了解,减少了对TS技术的恐惧感。
- 事件的监听和触发,两个项目都提供了一个编写的逻辑,提高了自己在发布订阅设计模式上的了解。