gs-events
Version:
426 lines (378 loc) • 19 kB
JavaScript
/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// @Copyright ~2018 ☜Samlv9☞ and other contributors
/// @MIT-LICENSE | 1.0.0 | https://api.guless.com/
/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// }|
/// }|
/// }| へ /|
/// _______ _______ ______ }| / │ / /
/// / ___ | |_ __ \ .' ____ '. }| │ Z _,< / /`ヽ
/// | (__ \_| | |__) | | (____) | }| │ ヽ / 〉
/// '.___`-. | __ / '_.____. | }| Y ` / /
/// |`\____) | _| | \ \_ | \____| | }| イ● 、 ● ⊂⊃〈 /
/// |_______.' |____| |___| \______,' }| () v | \〈
/// |=========================================\| >ー 、_ ィ │ //
/// |> LESS IS MORE || / へ / ノ<|\\
/// `=========================================/| ヽ_ノ (_/ │//
/// }| 7 |/
/// }| >―r ̄ ̄`ー―_`
/// }|
/// }|
/// Permission is hereby granted, free of charge, to any person obtaining a copy
/// of this software and associated documentation files (the "Software"), to deal
/// in the Software without restriction, including without limitation the rights
/// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
/// copies of the Software, and to permit persons to whom the Software is
/// furnished to do so, subject to the following conditions:
///
/// The above copyright notice and this permission notice shall be included in all
/// copies or substantial portions of the Software.
///
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
/// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
/// THE SOFTWARE.
import IEventDispatcher from "./IEventDispatcher";
import IEventListener from "./IEventListener";
import IEventListenerOptions from "./IEventListenerOptions";
import Event from "./Event";
import EventPhase from "./EventPhase";
import EventListener from "./EventListener";
/**
* 定义所有具备事件派发功能对象的基类。
* @implements {IEventDispatcher}
* @since 1.0.0
*/
export default class EventDispatcher /*< implements IEventDispatcher >*/ {
/**
* 创建一个事件派发器。
* @param {IEventDispatcher} [target=null] - 指定派发器的代理目标对象。
* @since 1.0.0
*/
constructor( target = null ) {
/**
* @type {IEventDispatcher}
*/
this._targetDispatcher = (target || this);
/**
* @type { {[type:String|Symbol]:Array<EventListener>} }
*/
this._listenerRegister = {};
/**
* @type { {type:String|Symbol}: Boolean }
*/
this._listenerLockers = {};
/** 事件代理 */
if ( this !== this._targetDispatcher ) {
if ( this._targetDispatcher.hasEventListener || this._targetDispatcher.addEventListener || this._targetDispatcher.removeEventListener || this._targetDispatcher.dispatchEvent ) {
throw new TypeError("无法为目标对象创建代理,因为其自身已经实现了一套事件系统。");
}
this._targetDispatcher.addEventListener = this.addEventListener .bind(this);
this._targetDispatcher.removeEventListener = this.removeEventListener .bind(this);
this._targetDispatcher.hasEventListener = this.hasEventListener .bind(this);
this._targetDispatcher.dispatchEvent = this.dispatchEvent .bind(this);
this._targetDispatcher._dispatchToListeners = this._dispatchToListeners.bind(this);
}
}
/**
* 注册一个事件侦听器。
*
* 如果浏览器支持 {@link Symbol} 类型,则事件类型可以使用 {@link Symbol} 对象,这样可以防止
* 你的事件被其他人意外的移除。
*
* 事件侦听器可以注册到派发事件的任何一个阶段,**捕获阶段**和**冒泡阶段**的侦听器是相互独立的。
* 因此使用不同的 `useCapture` 值将注册不同的侦听器。同样移除的时候,也需要指定不同的 `useCapture` 值。
*
* 事件处理函数默认的作用域为派发器(`EventDispatcher`)对象或者其代理的对象,通过设置 `options.scope`
* 可以为事件处理函数指定作用域。但是如果注册的是一个实现了 {@link IEventListener} 接口的对象,则直接
* 调用其 `handleEvent()` 方法处理事件,并且作用域始终指向该 `IEventListener` 对象。
*
* 通过指定 `options.once` 选项,可以指定侦听器在执行后,是否自动从列表中移除。
*
* 优先级高(`options.priority`)的侦听器会在派发事件时优先调用。相同优先级的情况会按照注册的先后
* 顺序调用侦听器。分别使用不同的优先级注册相同的侦听器时,以最后一次注册的侦听器为准。
*
* @param {String|Symbol} type - 事件类型。
* @param {Function|IEventListener} handler - 指定事件处理函数或者是一个事件侦听器。
* @param {Boolean|IEventListenerOptions} [options=false] - 指定侦听器配置选项。
* @example
* const dispatcher = new EventDispatcher();
*
* /// 在冒泡阶段注册侦听器。
* dispatcher.addEventListener("custom", ( evt ) => {});
* dispatcher.addEventListener("custom", ( evt ) => {}, false);
*
* /// 在捕获阶段注册侦听器。
* dispatcher.addEventListener("custom", ( evt ) => {}, true);
*
* /// 指定事件处理函数的作用域。
* const target = {};
* dispatcher.addEventListener("custom", ( evt ) => {
* console.log(target === this); // true
* }, { "scope": target });
*
* /// 注册一次性的侦听器。
* dispatcher.addEventListener("custom", ( evt ) => {
* /// 该函数执行后,将自动从列表中移除。
* }, { "once": true });
*
* /// 尽管该侦听器后注册的,但是因为具有较高的优先级,所以会先被调用。
* dispatcher.addEventListener("custom", ( evt ) => {
* console.log(1);
* }, { "priority": 1 });
*
* /// 相同的侦听器使用不用的优先级多次注册,以最后一次注册的优先级为准。
* const handler = ( evt ) => {};
* dispatcher.addEventListener("custom", handler, { "priority": 1 });
* dispatcher.addEventListener("custom", handler, { "priority": 2 }); // 只会注册一个优先级为 2 的侦听器。
* @see {@link IEventListenerOptions}
* @since 1.0.0
*/
addEventListener( type, handler, options = false ) {
if ( !handler ) {
throw new TypeError("handler 必须为事件处理函数(function)或者是事件侦听器(IEventListener)。");
}
const listener = new EventListener(handler, options);
/**
* 在事件派发过程中修改侦听器列表会导致派发顺序错误,因此这里需要拷贝侦听器列表的副本。
*
* @example
* const dispatcher = new EventDispatcher();
* dispatcher.addEventListener("custom", () => { // 如果不锁定侦听器列表的化,这里将产生一个死循环。
* console.log(1};
* dispatcher.addEventListener("custom", () => {}, { "priority": 1 });
* });
* dispatcher.dispatchEvent(new Event("custom", false, false));
*/
if ( this._listenerLockers[type] ) {
this._listenerLockers[type] = false;
this._listenerRegister[type] = this._listenerRegister[type].slice(0);
}
/// 移除重复的事件侦听器。
this.removeEventListener(type, handler, listener.options.useCapture);
/// 没有为 type 类型的事件注册侦听器。
if ( !this.hasEventListener(type) ) {
this._listenerRegister[type] = [listener];
return;
}
/// 为 type 类型已经注册的侦听器列表。
const items = this._listenerRegister[type];
/**
* 一般情况下,我们注册的侦听器的优先级都是默认的 0。因此这里可以先对比插入的侦听器的优先级是否为最低的优先级。
* 如果是则直接放入列表末尾。从而减少为插入侦听器而遍历列表的次数。
*/
if ( items[items.length - 1].options.priority >= listener.options.priority ) {
items.push(listener);
return;
}
/**
* 按照侦听器注册的顺序以及侦听器优先级的大小插入列表。
* - 1, 优先越高的侦听器放到列表的前面。
* - 2, 相同优先级的情况,先注册的侦听器放到列表的前面。
*/
let insertAt = items.length - 1;
while( ((insertAt >= 0) && (items[insertAt].options.priority < listener.options.priority)) ) { --insertAt; }
items.splice(1 + insertAt, 0, listener);
}
/**
* 移除一个事件侦听器。
*
* 第三个参数 `useCapture` 可以是一个 {@link Boolean} 类型的值,或者是一个 {@link IEventListenerOptions} 类型的值。
*
* 只有完全相同(使用 === 对比)的侦听器才会被移除。因此使用 `useCapture=false` 并不会移除**捕获阶段**的侦听器。
* 同理 `useCapture=true` 也不会移除**冒泡阶段**的侦听器。
*
* @param {String|Symbol} type - 事件类型。
* @param {Function|IEventListener} handler - 指定要移除的事件处理函数或者事件侦听器。
* @param {Boolean|IEventListenerOptions} [useCapture=false] - 指定是移除捕获阶段(`true`)还是冒泡阶段(`false`)的事件侦听器。
* @example
* /// 使用不用类型的 useCapture 值移除侦听器。
* dispatcher.removeEventListener(type, handler, false);
* dispatcher.removeEventListener(type, handler, { "useCapture": true });
*
* /// 分别移除不用阶段的侦听器。
* const dispatcher = new EventDispatcher();
*
* function captureHandler( evt ) {
* console.log("capture");
* }
*
* dispatcher.addEventListener("custom", captureHandler, true);
* dispatcher.removeEventListener("custom", captureHandler, false); // 这里并不会移除 captureHandler 侦听器。
* dispatcher.hasEventListener("custom"); // true
* dispatcher.removeEventListener("custom", captureHandler, true); // 这里才会移除 captureHandler 侦听器。
* dispatcher.hasEventListener("custom"); // false
* @since 1.0.0
*/
removeEventListener( type, handler, useCapture = false ) {
if ( !this.hasEventListener(type) ) {
return;
}
/**
* 如果 `useCapture` 是一个 {@link IEventListenerOptions} 对象,则获取 `IEventListenerOptions.useCapture` 的值。
*/
if ( typeof useCapture != "boolean" ) {
/** @type {IEventListenerOptions} */
const options = useCapture;
useCapture = (options.useCapture !== void 0 ? options.useCapture : false);
}
/**
* 在事件派发过程中修改侦听器列表会导致派发顺序错误,因此这里需要拷贝侦听器列表的副本。
* @see {@link EventDispatcher#addEventListener}
*/
if ( this._listenerLockers[type] ) {
this._listenerLockers[type] = false;
this._listenerRegister[type] = this._listenerRegister[type].slice(0);
}
for ( let i = 0, items = this._listenerRegister[type]; i < items.length; ++i ) {
/**
* 只有完全相同(使用 === 对比)的侦听器才会被移除。
*/
if ( handler === items[i].handler && useCapture === items[i].options.useCapture ) {
items.splice(i, 1);
break;
}
}
}
/**
* 检查是否注册了指定类型的事件侦听器。
* @param {String|Symbol} type - 事件类型。
* @example
* const dispatcher = new EventDispatcher();
* dispatcher.addEventListener("custom", ( evt ) => {}, false);
*
* console.log(dispatcher.hasEventListener("custom")); // true
* console.log(dispatcher.hasEventListener("notexists")); // false
* @returns {Boolean} - 如果存在指定类型的侦听器则返回 `true`,否则返回 `false`。
* @since 1.0.0
*/
hasEventListener( type ) {
return (!!this._listenerRegister[type] && this._listenerRegister[type].length >= 1);
}
/**
* 派发一个事件对象到目标对象的事件流中。
* @param {Event|String|Symbol} event - 指定派发的事件对象。
* @example
* const dispatcher = new EventDispatcher();
*
* dispatcher.dispatchEvent(new Event("custom", false, false)); // 派发一个不冒泡的事件。
* dispatcher.dispatchEvent(new Event("custom", true, false)); // 派发一个参与冒泡的事件。
* @returns {Boolean} - 如果事件传递到了当前目标对象,并且没有被取消默认行为。则返回 `true`,否则返回 `false`。
* @since 1.0.0
*/
dispatchEvent( event ) {
if ( typeof event == "string" || typeof event == "symbol" ) {
event = new Event(event);
}
/// 该事件对象已经被派发过一次!
if ( event.target || event.eventPhase !== EventPhase.NONE ) {
throw new Error("同一个事件对象不能派发多次!");
}
event._target = this._targetDispatcher; // 设置事件目标对象。
/**
* 满足以下条件时,直接调度事件至目标阶段:
*
* 1) 事件不参与冒泡行为。
* 2) 目标对象没有加入显示对象列表。
*/
if ( !event.bubbles || !event.target.parent
/*< 防止根级别对象循环引用 >*/ || (event.target === event.target.parent) ) {
event._eventPhase = EventPhase.AT_TARGET;
event._currentTarget = event.target;
if ( event.currentTarget._dispatchToListeners ) {
event.currentTarget._dispatchToListeners(event);
}
event._currentTarget = null; // 事件派发完成后,删除事件对当前目标的引用。
return !event.defaultPrevented;
}
/**
* 查找事件传递路径。
*/
let target = event.target;
let path = [target];
while( target.parent && target !== target.parent ) { path.push(target = target.parent); }
/**
* 捕获阶段。
*/
event._eventPhase = EventPhase.CAPTURING_PHASE;
for ( let i = path.length - 1; i >= 1 && !event._stopPropagation; --i ) {
event._currentTarget = path[i];
if ( event.currentTarget._dispatchToListeners ) {
event.currentTarget._dispatchToListeners(event);
}
}
if ( event._stopPropagation ) { // 事件在捕获阶段被中断。
event._currentTarget = null;
return false;
}
/**
* 目标阶段。
*/
event._eventPhase = EventPhase.AT_TARGET;
event._currentTarget = path[0];
if ( event.currentTarget._dispatchToListeners ) {
event.currentTarget._dispatchToListeners(event);
}
if ( event._stopPropagation ) {
event._currentTarget = null;
return !event.defaultPrevented;
}
/**
* 冒泡阶段。
*/
event._eventPhase = EventPhase.BUBBLING_PHASE;
for ( let i = 1; i < path.length && !event._stopPropagation; ++i ) {
event._currentTarget = path[i];
if ( event.currentTarget._dispatchToListeners ) {
event.currentTarget._dispatchToListeners(event);
}
}
event._currentTarget = null;
return !event.defaultPrevented;
}
/**
* 执行所有的侦听器。
* @param {Event} event - 指定派发的事件对象。
*/
_dispatchToListeners( event ) {
if ( !this.hasEventListener(event.type) ) {
return;
}
/**
* 锁定侦听器列表。
*/
this._listenerLockers[event.type] = true;
/**
* 执行侦听器
*/
for ( let i = 0, items = this._listenerRegister[event.type]; i < items.length && !event._stopImmediatePropagation; ++i ) {
/** @type {EventListener} */
const listener = items[i];
switch( event.eventPhase ) {
case EventPhase.CAPTURING_PHASE :
if ( listener.options.useCapture ) { listener.handleEvent(event); }
break;
case EventPhase.AT_TARGET :
listener.handleEvent(event);
break;
case EventPhase.BUBBLING_PHASE :
if ( !listener.options.useCapture ) { listener.handleEvent(event); }
break;
default: break;
}
/**
* 自动移除一次性的侦听器。
*/
if ( listener.options.once ) {
this.removeEventListener(event.type, listener.handler, listener.options.useCapture);
}
}
/**
* 解锁侦听器列表。
*/
this._listenerLockers[event.type] = false;
}
}