微前端架构如何改变企业的开发模式与效率提升
392
2024-06-15
这次给大家带来js的EventEmitter使用步奏详解,使用EventEmitter的注意事项有哪些,下面就是实战案例,一起来看一下。
2个多月前把 Github 上的 eventemitter3 和 Node.js 下的事件模块 events 的源码抄了一遍,才终于对 JavaScript 事件有所了解。
上个周末花点时间根据之前看源码的理解自己用 ES6 实现了一个 eventemitter8,然后也发布到 npm 上了,让我比较意外的是才发布两天在没有 readme 介绍,没有任何宣传的情况下居然有45个-,我很好奇都是谁-的,会不会用。我花了不少时间半抄半原创的一个 JavaScript 时间处理库 now.js (npm 传送门:now.js) ,在我大力宣传的情况下,4个月的-量才177。真是有心栽花花不开,无心插柳柳成荫!
eventemitter8 大部分是我根据看源码理解后写出来的,有一些方法如listeners,listenerCount 和 eventNames 一下子想不起来到底做什么,回头重查。测试用例不少是参考了 eventemitter3,在此对 eventemitter3 的开发者们和 Node.js 事件模块的开发者们表示感谢!
下面来讲讲我对 JavaScript 事件的理解:
从上图可以看出,JavaScript 事件最核心的包括事件监听 (addListener)、事件触发 (emit)、事件删除 (removeListener)。
事件监听(addListener)
首先,监听肯定要有监听的目标,或者说是对象,那为了达到区分目标的目的,名字是不可少的,我们定义为 type。
其次,监听的目标一定要有某种动作,对应到 JavaScript 里实际上就是某种方法,这里定义为 fn。
譬如可以监听一个 type 为 add,方法为某一个变量 a 值加1的方法 fn = () => a + 1的事件。如果我们还想监听一个使变量 b 加2的方法,我们第一反应可能是创建一个 type 为 add2,方法 为 fn1 = () => b + 2 的事件。你可能会想,这太浪费了,我能不能只监听一个名字,让它执行多于一个方法的事件。当然是可以的。
那么怎么做呢?
很简单,把监听的方法放在一个数组里,遍历数组顺序执行就可以了。以上例子变为 type 为 add,方法为[fn, fn1]。
如果要细分的话还可以分为可以无限次执行的事件 on 和 只允许执行一次的事件 once (执行完后立即将事件删除)。待后详述。
事件触发(emit)
单有事件监听是不够的,必须要有事件触发才能算完成整个过程。emit 就是去触发监听的特定 type 对应的单个事件或者一系列事件。拿前面的例子来说单个事件就是去执行 fn,一系列事件就是去遍历执行 fn 和 fn1。
事件删除(removeListener)
严格意义上来讲,事件监听和事件触发已经能完成整个过程。事件删除可有可无。但很多时候,我们还是需要事件删除的。比如前面讲的只允许执行一次事件 once,如果不提供删除方法,很难保证你什么时候会再次执行它。通常情况下,只要是不再需要的事件,我们都应该去删除它。
核心部分讲完,下面简单的对 eventemitter8的源码进行解析。
源码解析
全部源码:
const toString = Object.prototype.toString;
const isType = obj => toString.call(obj).slice(8, -1).toLowerCase();
const isArray = obj => Array.isArray(obj) || isType(obj) === 'array';
const isNullOrUndefined = obj => obj === null || obj === undefined;
const _addListener = function(type, fn, context, once) {
if (typeof fn !== 'function') {
throw new TypeError('fn must be a function');
}
fn.context = context;
fn.once = !!once;
const event = this._events[type];
// only one, let `this._events[type]` to be a function
if (isNullOrUndefined(event)) {
this._events[type] = fn;
} else if (typeof event === 'function') {
// already has one function, `this._events[type]` must be a function before
this._events[type] = [event, fn];
} else if (isArray(event)) {
// already has more than one function, just push
this._events[type].push(fn);
}
return this;
};
class EventEmitter {
constructor() {
if (this._events === undefined) {
this._events = Object.create(null);
}
}
addListener(type, fn, context) {
return _addListener.call(this, type, fn, context);
}
on(type, fn, context) {
return this.addListener(type, fn, context);
}
once(type, fn, context) {
return _addListener.call(this, type, fn, context, true);
}
emit(type, ...rest) {
if (isNullOrUndefined(type)) {
throw new Error('emit must receive at lease one argument');
}
const events = this._events[type];
if (isNullOrUndefined(events)) return false;
if (typeof events === 'function') {
events.call(events.context || null, rest);
if (events.once) {
this.removeListener(type, events);
}
} else if (isArray(events)) {
events.map(e => {
e.call(e.context || null, rest);
if (e.once) {
this.removeListener(type, e);
}
});
}
return true;
}
removeListener(type, fn) {
if (isNullOrUndefined(this._events)) return this;
// if type is undefined or null, nothing to do, just return this
if (isNullOrUndefined(type)) return this;
if (typeof fn !== 'function') {
throw new Error('fn must be a function');
}
const events = this._events[type];
if (typeof events === 'function') {
events === fn && delete this._events[type];
} else {
const findIndex = events.findIndex(e => e === fn);
if (findIndex === -1) return this;
// match the first one, shift faster than splice
if (findIndex === 0) {
events.shift();
} else {
events.splice(findIndex, 1);
}
// just left one listener, change Array to Function
if (events.length === 1) {
this._events[type] = events[0];
}
}
return this;
}
removeAllListeners(type) {
if (isNullOrUndefined(this._events)) return this;
// if not provide type, remove all
if (isNullOrUndefined(type)) this._events = Object.create(null);
const events = this._events[type];
if (!isNullOrUndefined(events)) {
// check if `type` is the last one
if (Object.keys(this._events).length === 1) {
this._events = Object.create(null);
} else {
delete this._events[type];
}
}
return this;
}
listeners(type) {
if (isNullOrUndefined(this._events)) return [];
const events = this._events[type];
// use `map` because we need to return a new array
return isNullOrUndefined(events) ? [] : (typeof events === 'function' ? [events] : events.map(o => o));
}
listenerCount(type) {
if (isNullOrUndefined(this._events)) return 0;
const events = this._events[type];
return isNullOrUndefined(events) ? 0 : (typeof events === 'function' ? 1 : events.length);
}
eventNames() {
if (isNullOrUndefined(this._events)) return [];
return Object.keys(this._events);
}
}
export default EventEmitter;
代码很少,只有151行,因为写的简单版,且用的 ES6,所以才这么少;Node.js的事件和 eventemitter3可比这多且复杂不少,有兴趣可自行深入研究。
const toString = Object.prototype.toString;
const isType = obj => toString.call(obj).slice(8, -1).toLowerCase();
const isArray = obj => Array.isArray(obj) || isType(obj) === 'array';
const isNullOrUndefined = obj => obj === null || obj === undefined;
这4行就是一些工具函数,判断所属类型、判断是否是 null 或者 undefined。
constructor() {
if (isNullOrUndefined(this._events)) {
this._events = Object.create(null);
}
}
创建了一个 EventEmitter 类,然后在构造函数里初始化一个类的 _events 属性,这个属性不需要要继承任何东西,所以用了 Object.create(null)。当然这里 isNullOrUndefined(
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~