# mitt、tiny-emitter 发布订阅

发布订阅模式:当使用者订阅了某一个对象,当对象发生对应的动作之后,会通知到使用者事先设定的函数。

在前端中举个简单的例子,对按钮点击事件的监听。当按钮被点击之后,某一个事先设定的函数会被调用,借助的是 addEventListener

# 环境准备

克隆两个仓库

git clone https://github.com/developit/mitt.git
git clone https://github.com/scottcorgan/tiny-emitter.git

# tiny-emitter

详细使用 (opens new window)

克隆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;

编写了一个函数,在函数的原型链上提供了四个方法

  1. on: 监听一个事件
  2. once:一次性监听一个事件
  3. emit: 触发事件
  4. 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

详细使用 (opens new window)

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!);
            });
    }
}

# 收获

  1. 两个项目的对比,一个js和一个TS,被动中增加了对TS的了解,减少了对TS技术的恐惧感。
  2. 事件的监听和触发,两个项目都提供了一个编写的逻辑,提高了自己在发布订阅设计模式上的了解。

# 参考资料