@writ/utils
Version:
My tool kit
378 lines (332 loc) • 11.5 kB
JavaScript
'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;