just-event.js
Version:
Modern, lightweight event utility with jQuery-like API.
373 lines (346 loc) • 13.9 kB
JavaScript
var event = (function () {
'use strict';
function _classPrivateFieldLooseBase(e, t) {
if (!{}.hasOwnProperty.call(e, t)) throw new TypeError("attempted to use private field on non-instance");
return e;
}
var id = 0;
function _classPrivateFieldLooseKey(e) {
return "__private_" + id++ + "_" + e;
}
/** Returns the object type of the given payload */
function getType(payload) {
return Object.prototype.toString.call(payload).slice(8, -1);
}
/** Returns whether the payload is an array */
function isArray(payload) {
return getType(payload) === 'Array';
}
/** Returns whether the payload is a string */
function isString(payload) {
return getType(payload) === 'String';
}
/** Returns whether the payload is a function (regular or async) */
function isFunction(payload) {
return typeof payload === 'function';
}
/**
* Does a generic check to check that the given payload is of a given type. In cases like Number, it
* will return true for NaN as NaN is a Number (thanks javascript!); It will, however, differentiate
* between object and null
*
* @throws {TypeError} Will throw type error if type is an invalid type
*/
function isType(payload, type) {
if (!(type instanceof Function)) {
throw new TypeError('Type must be a function');
}
if (!Object.prototype.hasOwnProperty.call(type, 'prototype')) {
throw new TypeError('Type is not a class');
}
// Classes usually have names (as functions usually have names)
const name = type.name;
return getType(payload) === name || Boolean(payload && payload.constructor === type);
}
function isInstanceOf(value, classOrClassName) {
if (typeof classOrClassName === 'function') {
for (let p = value; p; p = Object.getPrototypeOf(p)) {
if (isType(p, classOrClassName)) {
return true;
}
}
return false;
}
else {
for (let p = value; p; p = Object.getPrototypeOf(p)) {
if (getType(p) === classOrClassName) {
return true;
}
}
return false;
}
}
var Utils = {
// 处理传入的参数
normalizeElements(selector) {
if (isString(selector)) {
return Array.from(document.querySelectorAll(selector));
}
if (this.isDomElement(selector)) {
return [selector];
}
// 如果本身传递的就是Nodelist
if (isInstanceOf(selector, "NodeList") || isArray(selector)) {
return Array.from(selector).filter(this.isDomElement);
}
// 无效输入:返回空数组
console.warn("Invalid selector:", selector);
return [];
},
// 特殊的对象
isDomElement(el) {
return isInstanceOf(el, "HTMLElement") || isInstanceOf(el, "Document") || isInstanceOf(el, "Window");
},
closestUntil(el, selector, stopElm) {
if (!el || !selector) return null;
while (el) {
if (el.matches(selector)) return el;
if (el === stopElm || el === document.body) break;
el = el.parentElement;
}
return null;
}
};
const byteToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex.push((i + 0x100).toString(16).slice(1));
}
function unsafeStringify(arr, offset = 0) {
return (byteToHex[arr[offset + 0]] +
byteToHex[arr[offset + 1]] +
byteToHex[arr[offset + 2]] +
byteToHex[arr[offset + 3]] +
'-' +
byteToHex[arr[offset + 4]] +
byteToHex[arr[offset + 5]] +
'-' +
byteToHex[arr[offset + 6]] +
byteToHex[arr[offset + 7]] +
'-' +
byteToHex[arr[offset + 8]] +
byteToHex[arr[offset + 9]] +
'-' +
byteToHex[arr[offset + 10]] +
byteToHex[arr[offset + 11]] +
byteToHex[arr[offset + 12]] +
byteToHex[arr[offset + 13]] +
byteToHex[arr[offset + 14]] +
byteToHex[arr[offset + 15]]).toLowerCase();
}
let getRandomValues;
const rnds8 = new Uint8Array(16);
function rng() {
if (!getRandomValues) {
if (typeof crypto === 'undefined' || !crypto.getRandomValues) {
throw new Error('crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported');
}
getRandomValues = crypto.getRandomValues.bind(crypto);
}
return getRandomValues(rnds8);
}
const randomUUID = typeof crypto !== 'undefined' && crypto.randomUUID && crypto.randomUUID.bind(crypto);
var native = { randomUUID };
function v4(options, buf, offset) {
if (native.randomUUID && true && !options) {
return native.randomUUID();
}
options = options || {};
const rnds = options.random ?? options.rng?.() ?? rng();
if (rnds.length < 16) {
throw new Error('Random bytes length must be >= 16');
}
rnds[6] = (rnds[6] & 0x0f) | 0x40;
rnds[8] = (rnds[8] & 0x3f) | 0x80;
return unsafeStringify(rnds);
}
// 用于存储所有事件监听器
const listenersMap = new Map();
var _namespaceSeparator = /*#__PURE__*/_classPrivateFieldLooseKey("namespaceSeparator");
var _eventNameKey = /*#__PURE__*/_classPrivateFieldLooseKey("eventNameKey");
var _getEventKey = /*#__PURE__*/_classPrivateFieldLooseKey("getEventKey");
var _getRealEventType = /*#__PURE__*/_classPrivateFieldLooseKey("getRealEventType");
var _getEvents = /*#__PURE__*/_classPrivateFieldLooseKey("getEvents");
var _resolveArgs = /*#__PURE__*/_classPrivateFieldLooseKey("resolveArgs");
var _clean = /*#__PURE__*/_classPrivateFieldLooseKey("clean");
class Event {
constructor(selector) {
// 清理事件
Object.defineProperty(this, _clean, {
value: _clean2
});
// 验证参数是否传递了类型为函数的listener
Object.defineProperty(this, _resolveArgs, {
value: _resolveArgs2
});
// 获取事件数组
Object.defineProperty(this, _getEvents, {
value: _getEvents2
});
// 获取事件类型
Object.defineProperty(this, _getRealEventType, {
value: _getRealEventType2
});
// 为元素和事件生成唯一键
Object.defineProperty(this, _getEventKey, {
value: _getEventKey2
});
// 命名空间分隔符
Object.defineProperty(this, _namespaceSeparator, {
writable: true,
value: "."
});
// 事件名称保存的key值
Object.defineProperty(this, _eventNameKey, {
writable: true,
value: "__eventName"
});
// 获取目标元素,可以是单个 DOM 元素或多个元素
this.elements = Utils.normalizeElements(selector);
}
// 为事件绑定监听器,支持委托
on(eventNames, selectorOrListener, listener) {
const resolved = _classPrivateFieldLooseBase(this, _resolveArgs)[_resolveArgs](selectorOrListener, listener);
if (!resolved) return this;
const {
isDelegated
} = resolved;
listener = resolved.listener;
for (const eventName of _classPrivateFieldLooseBase(this, _getEvents)[_getEvents](eventNames)) {
for (const el of this.elements) {
const eventKey = _classPrivateFieldLooseBase(this, _getEventKey)[_getEventKey](el, eventName);
const handler = event => {
// 保持和JQuery相同的行为
event.delegateTarget = el;
if (isDelegated) {
const matchedTarget = Utils.closestUntil(event.target, selectorOrListener, el);
if (!matchedTarget) return;
// 保持和JQuery相同的行为
Object.defineProperty(event, "currentTarget", {
value: matchedTarget
});
}
// 保持和jQuery的行为相同,让trigger方法支持命名空间
const triggerEventName = event[_classPrivateFieldLooseBase(this, _eventNameKey)[_eventNameKey]];
if (triggerEventName && !eventKey.startsWith(triggerEventName)) return;
listener.call(event.currentTarget, event);
};
if (!listenersMap.has(eventKey)) listenersMap.set(eventKey, []);
listenersMap.get(eventKey).push(handler); // 缓存监听器
el.addEventListener(_classPrivateFieldLooseBase(this, _getRealEventType)[_getRealEventType](eventName), handler); // 绑定事件
}
}
return this;
}
// 绑定一次性事件监听器
one(eventNames, selectorOrListener, listener) {
const resolved = _classPrivateFieldLooseBase(this, _resolveArgs)[_resolveArgs](selectorOrListener, listener);
if (!resolved) return this;
const {
isDelegated
} = resolved;
listener = resolved.listener;
for (const eventName of _classPrivateFieldLooseBase(this, _getEvents)[_getEvents](eventNames)) {
const onceHandler = event => {
this.off(eventName); // 移除事件
listener.call(event.currentTarget, event);
};
if (isDelegated) {
this.on(eventName, selectorOrListener, onceHandler);
} else {
this.on(eventName, onceHandler);
}
}
return this;
}
// 移除事件监听器
off(eventNames) {
for (const eventName of _classPrivateFieldLooseBase(this, _getEvents)[_getEvents](eventNames)) {
for (const el of this.elements) {
// 传入的事件key
const inputEventKey = _classPrivateFieldLooseBase(this, _getEventKey)[_getEventKey](el, eventName); // 带命名空间的前缀
if (eventName.startsWith(_classPrivateFieldLooseBase(this, _namespaceSeparator)[_namespaceSeparator])) {
// 遍历事件映射,找出所有匹配命名空间的 key
for (const [eventKey, listeners] of listenersMap) {
eventKey.endsWith(inputEventKey) && _classPrivateFieldLooseBase(this, _clean)[_clean](el, eventKey.replace(inputEventKey, ""),
//直接得到没有命名空间的事件名
eventKey, listeners);
}
} else {
// 事件+命名空间
if (eventName.includes(_classPrivateFieldLooseBase(this, _namespaceSeparator)[_namespaceSeparator])) {
const listeners = listenersMap.get(inputEventKey);
listeners && _classPrivateFieldLooseBase(this, _clean)[_clean](el, eventName, inputEventKey, listeners);
} else {
// 只包含事件
for (const [eventKey, listeners] of listenersMap) {
eventKey.startsWith(eventName) && _classPrivateFieldLooseBase(this, _clean)[_clean](el, eventName, eventKey, listeners);
}
}
}
}
}
return this;
}
// 触发自定义事件,支持命名空间
trigger(eventNames, detail = null) {
for (const eventName of _classPrivateFieldLooseBase(this, _getEvents)[_getEvents](eventNames)) {
const realEventName = _classPrivateFieldLooseBase(this, _getRealEventType)[_getRealEventType](eventName);
for (const el of this.elements) {
// 命名空间触发,如 click.app1
if (eventName.includes(_classPrivateFieldLooseBase(this, _namespaceSeparator)[_namespaceSeparator])) {
for (const [eventKey] of listenersMap) {
if (eventKey.startsWith(eventName)) {
const namespaceEvent = new CustomEvent(realEventName, {
bubbles: true,
detail
});
// 记录该属性在on方法中根据此属性判断是否调用
Object.defineProperty(namespaceEvent, _classPrivateFieldLooseBase(this, _eventNameKey)[_eventNameKey], {
value: eventName,
writable: false,
// 不可写
configurable: false,
// 不可删除或重新定义
enumerable: true // 可枚举(可选)
});
// 让让支持冒泡,并传递当前的命名空间
el.dispatchEvent(namespaceEvent);
}
}
} else {
// 仅事件类型触发,如 click,走原生 dispatch
el.dispatchEvent(new CustomEvent(eventName, {
bubbles: true,
detail
}));
}
}
}
return this;
}
}
function _getEventKey2(el, eventName) {
if (!el.__eventListenerUUID) {
// 如果元素没有关联 UUID,则生成一个
el.__eventListenerUUID = v4();
}
return `${eventName}__${el.__eventListenerUUID}`; // 唯一键格式:事件名 + UUID
}
function _getRealEventType2(eventName) {
// click.app1、click 都只得到真正的事件类型 click
return eventName.split(_classPrivateFieldLooseBase(this, _namespaceSeparator)[_namespaceSeparator])[0];
}
function _getEvents2(eventName) {
return !isString(eventName) ? [] : eventName.trim().split(/\s+/);
}
function _resolveArgs2(selectorOrListener, listener) {
const isDelegated = isString(selectorOrListener);
listener = isDelegated ? listener : selectorOrListener;
if (!isFunction(listener)) return null;
return {
isDelegated,
listener
};
}
function _clean2(el, eventName, eventKey, listeners) {
for (const listener of listeners) {
el.removeEventListener(_classPrivateFieldLooseBase(this, _getRealEventType)[_getRealEventType](eventName), listener);
}
listenersMap.delete(eventKey);
}
const $ = selector => {
return new Event(selector);
};
return $;
})();
//# sourceMappingURL=just-event.browser.js.map