UNPKG

@writ/utils

Version:
378 lines (332 loc) 11.5 kB
'use strict'; import isDOM from './is-dom'; import isWindow from '../utils-client/is-window'; import addEvent from './event-add'; import removeEvent from './event-remove'; import triggerEvent from './event-trigger'; /** * 事件模型 * @constructor Model **/ class EventModel { /** * @param {object} event 原生JS事件对象 * @param {*} data 传递给事件模型的数据 * @desc IE模型中event是一个全局唯一的对象绑定在window对象上 * @desc 这里使用对象的自身属性和方法 */ constructor(event, data = null) { this.originalEvent = event ? event : window.event; // 原生事件模型 this.type = this.originalEvent.type; // 事件对象类型 this.target = this.originalEvent.target || this.originalEvent.srcElement; // 事件发生目标 this.timestamp = this.originalEvent.timeStamp || Date.now(); // 事件发生时间 this.emited = Boolean(this.originalEvent.emited); // 事件是否自动触发 this.data = data; // 事件模型携带的数据 } /** * 阻止默认事件 * * @memberOf **/ preventDefault() { const evt = this.originalEvent; if (evt.preventDefault) { evt.preventDefault(); } else { evt.returnValue = false; } } /** * 阻止冒泡 * * @memberOf **/ stopPropagation() { const evt = this.originalEvent; if (evt.stopPropagation) { evt.stopPropagation(); } else { evt.cancelBubble = true; } } /** * 阻止同类监听器 * 阻止事件冒泡,如果几个监听器被绑定到同一个元素的相同事件上 * 则按照它们被添加的顺序调用它们, * 如果有一个调用期间调用了event.stopImmediatePropagation(),则不会调用剩余的监听器。 * * @memberOf **/ stopImmediatePropagation() { const evt = this.originalEvent; if (evt.stopImmediatePropagation) { evt.stopImmediatePropagation(); } else { evt.cancelBubble = true; } } } /** * 事件管理器 * * -------------------------------------------- 实例属性、方法 * @method getMaxListeners() ✅ 获取当前事件的最大监听器数量 * @method setMaxListeners(n) ✅ 设置当前事件的最大监听器数量 * @method eventNames() ✅ 获取实例绑定的事件处理目标绑定的事件名称的集合[Array] * @method listeners(eventName) ✅ 根据传入的事件名称获取其绑定的监听器数量,传入的事件不存在返回0,事件名称错误时抛出一个异常 * @method on(eventName,listener,[options]) ✅ 绑定事件监听器,参数0:事件名称,参数1:事件监听器函数,参数2:是一个对象 * @method once(eventName,listener,[options]) ✅ 绑定一次性事件监听器,参数同 on, 在on的基础上实现的 * @method off([eventName],[listener]) ✅ 解绑事件监听器,当不传递参数时,会默认解绑所有监听器 * @method emit(eventName,[...args]) ✅ 主动触发指定的事件,多余的参数将传递给处理函数,可以使用 e.data来访问,同时,e.emited变为true,表示当前事件为主动触发 * @method prep(eventName,listener,[options]) ✅ 前置事件监听器,在 on 的基础上实现,参数同 on * * -------------------------------------------- 静态属性、方法 * @default defaultMaxListeners ✅ * @default addEventListener ✅ DOM绑定事件 * @default removeEventListener ✅ DOM删除事件 * @default emitEventListener ✅ DOM触发事件或伪造事件模型 **/ export class EventEmiter { constructor(element) { if (!isDOM(element) && !isWindow(element)) { throw new TypeError('[Event Error] This object must be an HTML element.'); } // 事件绑定目标 this._target = element; // 默认事件监听器的最大个数 this._maxListeners = EventEmiter.defaultMaxListeners; // 事件队列 this._queue = {}; } /** * 获取事件监听的最大个数 **/ getMaxListeners() { return this._maxListeners; } /** * 默认情况下,如果为特定事件添加了超过 10 个监听器,则 EventEmitter 会打印一个警告。 * 此限制有助于寻找内存泄露 * * @param {number} n */ setMaxListeners(n) { this._maxListeners = n < 0 ? EventEmiter.defaultMaxListeners : Math.floor(n); } /** * * @description 返回一个列出触发器已注册监听器的事件的数组, 数组中的值为字符串或符号。 **/ eventNames() { return Object.keys(this._queue); } /** * 获取指定数据的监听器的数量 * @param {string} eventName */ listeners(eventName) { if ('string' === typeof eventName) { if (Reflect.has(eventName)) { return this._queue[eventName].length; } else { return 0 } } throw new TypeError('参数类型错误,事件名称应该是字符串类型'); } /** * 绑定事件监听 * * @param {string} eventName 事件名称 * @param {function} listener 事件处理函数 * @param {object} options 传递给事件对象的配置 * @param {boolean} options.once 一次性监听,由于目前原生 addEventListener 的 'once' 参数存在兼容性问题, * 因此采用绑定一次,删除一次的方式来实现 * @param {boolean} options.capture 事件捕获 * @param {boolean} options.passive 是否无效preventDefault * @param {boolean} options.once 是否触发一次 * @param {boolean} options.prep 是否为前置触发器 */ on(eventName, listener, { data = null, capture = false, passive = false, once = false, prep = false } = options) { if ('string' !== typeof eventName) { throw new TypeError('The event name must be a string.'); // 确保 eventName 是字符串 } if ('function' !== typeof listener) { throw new TypeError('The event listener must be a function.'); // 确保 listener 是函数 } if ('object' !== typeof options) { throw new TypeError('The options must be an object.'); // 确保 options 是对象 } this._queue[eventName] = this._queue[eventName] || []; const refer = this._target; const queue = this._queue[eventName]; let actual, index; if (listener.refer === refer && listener.timestamp) { actual = listener; } else { // 实际的事件监听器 actual = function (event) { const realEvent = new EventModel(event, actual.data); actual.listener.call(actual.refer, realEvent); if (actual.once === true) { EventEmiter.removeEventListener(actual.refer, actual.eventName, actual, { passive: actual.passive, capture: actual.capture }); actual.queue.splice(actual.queue.indexOf(actual), 1); // 从当前事件队列里踢出 } }; actual.eventName = eventName; actual.refer = refer; actual.listener = listener; actual.data = data; actual.passive = passive; actual.capture = capture; actual.once = once; actual.timestamp = Date.now(); actual.queue = queue; } if (prep) { // 加长数组 queue.unshift(actual); index = 0; } else { queue.push(actual); index = queue.length - 1; } EventEmiter.addEventListener(refer, eventName, actual, { capture, passive }); if (queue.length >= this._maxListeners && this._maxListeners !== 0 && this._maxListeners !== Infinity) { console.warn('[EventEmiter Warn]', `事件'${eventName}'的监听器数量已超过${this._maxListeners},可能会造成内存溢出等异常`) } return this; }; /** * 前置事件监听程序 * @param {string} eventName 事件名称 * @param {function} listener 监听程序 * @desc 参数同 on */ prep(eventName, listener, { data = null, capture = false, passive = false, once = false }) { let queue = this._queue[eventName] || []; let temps = []; for (let index = 0; index < queue.length; index++) { const actual = queue[index]; temps[index] = actual; EventEmiter.removeEventListener(actual.refer, actual.eventName, actual, { capture: actual.capture, passive: actual.passive }); queue.splice(index, 1); } if (queue.length !== 0) { // 保证原始队列清空 queue = []; this._queue[eventName] = queue; } this.on(eventName, listener, { data, once, capture, passive, prep: true }); for (let index = 0; index < temps.length; index++) { const actual = queue[index]; this.on(actual.eventName, actual, { data: actual.data, once: actual.once, capture: actual.capture, passive: actual.passive, prep: false }); } temps = null; return this; } /** * 绑定执行一次的事件监听 * * @param {string} eventName 事件名称 * @param {function} listener 事件处理函数 * @param {object} options 传递给事件模型的数据 **/ once(eventName, listener, { data = null, capture = false, passive = false }) { this.on(eventName, listener, { data, capture, passive, once: true }); return this; }; /** * 移除指定的事件监听 * * @param {string} eventName 事件名称 * @param {function} listener 需要移除的事件处理函数的名称 **/ off(eventName, listener) { const refer = this._target; if ('string' === typeof eventName) { const queue = this._queue[eventName] || []; const hasListener = 'function' === typeof listener; for (let index = 0; index < queue.length; index++) { const actual = queue[index]; if (!hasListener) { EventEmiter.removeEventListener(refer, eventName, actual, { capture: actual.capture, passive: actual.passive }); } else { if (listener === actual.listener) { EventEmiter.removeEventListener(refer, eventName, actual, { capture: actual.capture, passive: actual.passive }); queue.splice(index, 1); break; } } } if (!hasListener) { queue.length = 0; Reflect.deleteProperty(this._queue, eventName); } } else if (!eventName) { // 一个参数都没提供, 直接清空 queue for (let name in this._queue) { const queue = this._queue[name]; for (let index = 0; index < queue.length; index++) { const actual = queue[index]; EventEmiter.removeEventListener(refer, name, actual, { capture: actual.capture, passive: actual.passive }); } queue.length = 0; } this._queue = {}; } return this; }; /** * 主动触发指定的事件监听 * 按监听器的注册顺序,同步地调用每个注册到名为 eventName 事件的监听器,并传入提供的参数。 * 如果事件有监听器,则返回 true ,否则返回 false。 * * @param {string} eventName */ emit(eventName, data) { if ('string' === typeof eventName) { const queue = this._queue[eventName] || []; const fakeEventModel = EventEmiter.emitEventListener; // 伪造事件模型 for (const actual of queue) { actual.data = data ? data : actual.data; const fakedEvent = fakeEventModel(actual.refer, actual.eventName, true); actual(fakedEvent); } } return this; }; } /** * 全局基础配置 * 所有 EventEmitter 实例的共享 * * @default defaultMaxListeners 内置最大监听数量,超过这个数量会抛出一个警告 * @default addEventListener DOM原生的事件注册函数 * @default removeEventListener DOM原生的事件销毁函数 * @default emitEventListener DOM原生的事件自动触发与伪造事件模型函数 **/ EventEmiter.defaultMaxListeners = 10; EventEmiter.addEventListener = addEvent; EventEmiter.removeEventListener = removeEvent; EventEmiter.emitEventListener = triggerEvent;