addEventListener实现原理

7,518 阅读3分钟

MDN介绍 developer.mozilla.org/zh-CN/docs/…

场景

当按下键盘上的加减键时,调整漫游速度,同时弹窗提醒。

因为漫游操作器封装成了一个对象,所以当速度改变时要通知外部调用这个对象者做成相应动作

初步代码思路:

如图所示

  • 1.绑定好速度改变的回调函数
  • 2.业务执行cameraWalkOperator.changeSpeed(value)
  • 3.当速度改变时,会执行先前绑定好的操作步骤,弹窗显示

优化

以上代码虽然实现了需求场景,但是代码不太友好,也不好维护。试着进行如下抽象

  • 1.创建一个listeners对象,对象上的每个属性代表一种监听类型。
  • 2.listeners对象的type属性是一个数组,用来存放监听事件。
  • 3.一二两步已经把监听事件绑定好了,现在就差触发。写一个触发函数,传入事件类型,查找监听的事件并执行。

以上代码虽然看起来多了复杂了,其实理解了并不复杂,就是最原生的对象和数组的结合使用。

想象一下场景,有三种不同类型的事件要监听触发不同的操作,如果采用一开始的代码思路,则要复制三个相同的代码,比较分散,代码也有冗余,耦合度较高。现在用一个对象包裹,不同的监听类型代表这个对象上的属性,对象上的属性值存在数组用来保存监听事件。这样所有的监听都在一个对象上,很集中,没有冗余,与外界解耦,相当于一个模块。

为了便于其它地方也能使用这个事件监听,把它封装成一个构造函数。

EventDispatcher构造函数

/**
 * @author [Wu-Qin-Hao]
 * @email [1539849378@qq.com]
 * @create date 2020-08-03 10:55:00
 * @modify date 2020-08-03 10:55:00
 * @desc [自定义事件监听-观察者模式]
 */
function EventDispatcher() {}

Object.assign( EventDispatcher.prototype, {
	// 添加事件监听
 	// @type 类型,用于区分查找
 	// @listener 函数,用于监听执行
	addEventListener: function ( type, listener ) {
		// 初始化_listeners为{}
		if ( this._listeners === undefined ) this._listeners = {};
		// 获取_listeners内容
		var listeners = this._listeners;
		// 如果_listeners对象上没有 type属性 的数据则初始化为[]
		if ( listeners[ type ] === undefined ) {
        		listeners[ type ] = [];
		}
		// 如果_listeners对象的type属性值的数组里没有listener函数,则push进数组
		if ( listeners[ type ].indexOf( listener ) === - 1 ) {
			listeners[ type ].push( listener );
		}
	},

	// 判断是否有这个事件监听
 	// @type 类型,用于区分查找
 	// @listener 函数,用于监听执行
	hasEventListener: function ( type, listener ) {
		// 如果_listeners未定义,则直接退出
		if ( this._listeners === undefined ) return false;
		// 获取_listeners内容
		var listeners = this._listeners;
		// 在_listeners对象上查找type属性,没有直接返回false,有则继续查找这个属性内容数组里是否有listener这个函数,有则返回true,否则返回false
		return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1;
	},
	// 删除事件监听
	// @type 类型,用于区分查找
 	// @listener 函数,用于监听执行
	removeEventListener: function ( type, listener ) {
		// 如果_listeners未定义,则直接退出
		if ( this._listeners === undefined ) return;
		// 获取_listeners内容
		var listeners = this._listeners;
		// _listeners对象上type属性的内容
		var listenerArray = listeners[ type ];
		// 如果有值
		if ( listenerArray !== undefined ) {
        	// 查找listener监听函数是否存在
			var index = listenerArray.indexOf( listener );
			// 如果存在则在数组中删除
			if ( index !== - 1 ) {
				listenerArray.splice( index, 1 );
			}
		}
	},
	// 触发监听函数
	dispatchEvent: function ( event ) {
		// 如果_listeners未定义,则直接退出
		if ( this._listeners === undefined ) return;
		// 获取_listeners内容
		var listeners = this._listeners;
		// _listeners对象上type属性的内容
		var listenerArray = listeners[ event.type ];
		// 如果有值
		if ( listenerArray !== undefined ) {
			// 在event对象上增加target属性,把this对象传给target,后续外部调用者可能需要使用,先保存
			event.target = this;
			// slice方法并不会修改数组,而是返回一个新数组
			var array = listenerArray.slice( 0 );
			// 遍历event.type类型下的所有事件监听
			for ( var i = 0, l = array.length; i < l; i ++ ) {
				// 调用监听事件
				array[ i ].call( this, event );
			}
		}
	}
} );

export { EventDispatcher };

有关 构造函数,原型链。请看我的这篇文章 juejin.cn/post/684490…