CKEditor5事件系统源码分析(二)

上一节我们分析了CK5的EmitterMixin类的重点方法addEventListener,今天我们再认真看看fire方法。
fire( eventOrInfo, ...args ) {
try {
const eventInfo = eventOrInfo instanceof EventInfo ? eventOrInfo : new EventInfo( this, eventOrInfo );
const event = eventInfo.name;
let callbacks = getCallbacksForEvent( this, event );
// Record that the event passed this emitter on its path.
eventInfo.path.push( this );
// Handle event listener callbacks first.
if ( callbacks ) {
// Arguments passed to each callback.
const callbackArgs = [ eventInfo, ...args ];
// Copying callbacks array is the easiest and most secure way of preventing infinite loops, when event callbacks
// are added while processing other callbacks. Previous solution involved adding counters (unique ids) but
// failed if callbacks were added to the queue before currently processed callback.
// If this proves to be too inefficient, another method is to change `.on()` so callbacks are stored if same
// event is currently processed. Then, `.fire()` at the end, would have to add all stored events.
callbacks = Array.from( callbacks );
for ( let i = 0; i < callbacks.length; i++ ) {
callbacks[ i ].callback.apply( this, callbackArgs );
// Remove the callback from future requests if off() has been called.
if ( eventInfo.off.called ) {
// Remove the called mark for the next calls.
delete eventInfo.off.called;
this._removeEventListener( event, callbacks[ i ].callback );
}
// Do not execute next callbacks if stop() was called.
if ( eventInfo.stop.called ) {
break;
}
}
}
// Delegate event to other emitters if needed.
if ( this._delegations ) {
const destinations = this._delegations.get( event );
const passAllDestinations = this._delegations.get( '*' );
if ( destinations ) {
fireDelegatedEvents( destinations, eventInfo, args );
}
if ( passAllDestinations ) {
fireDelegatedEvents( passAllDestinations, eventInfo, args );
}
}
return eventInfo.return;
} catch ( err ) {
// @if CK_DEBUG // throw err;
/* istanbul ignore next */
CKEditorError.rethrowUnexpectedError( err, this );
}
},
重点方法
这里面有一个重点方法:
// Get the list of callbacks for a given event, but only if there any callbacks have been registered.
// If there are no callbacks registered for given event, it checks if this is a specific event and looks
// for callbacks for it's more generic version.
function getCallbacksForEvent( source, eventName ) {
let event;
if ( !source._events || !( event = source._events[ eventName ] ) || !event.callbacks.length ) {
// There are no callbacks registered for specified eventName.
// But this could be a specific-type event that is in a namespace.
if ( eventName.indexOf( ':' ) > -1 ) {
// If the eventName is specific, try to find callback lists for more generic event.
return getCallbacksForEvent( source, eventName.substr( 0, eventName.lastIndexOf( ':' ) ) );
} else {
// If this is a top-level generic event, return null;
return null;
}
}
return event.callbacks;
}
注意,这里获取指定事件的回调函数列表的时候,举个例子来说吧,如果指定的事件名称是insert:p;它会先寻找这个事件对应的回调函数列表,如果不存在,则会寻找insert事件名称对应的回调函数;因此当我们调用emitters.fire('insert:p', args)的时候,监听insert:p 事件和监听insert事件的回调函数都会执行。
这里有一个疑问?如果这个回去回调函数列表的方法已经注册了insert:p事件,那么它就不会往下寻找更通用的insert事件,这个又是怎么出发insert事件的回调函数呢?这里留一个疑问哈。
当我们获取回调函数列表后,会构建EventInfo对象,然后将当前的emitter放到EventInfo的path路径中,这样我们就方便知道事件的传播路径啦。
剩下的自然就是迭代回调函数,然后执行。在执行的过程中,我们看到一些逻辑,如果这个事件调用了off方法,那么这个回调函数将取消监听;而如果这个事件调用了stop()方法,那么回调函数的迭代将结束;这就是有的时候我们需要用自定义的回调屏蔽掉一些不必要的回调的时候,我们就调用event.stop()即可。
剩下的部分代码是关于事件代理的,这个我们以后在分析,当这些迭代执行完成后,我们返回eventInfo.return属性,这就是我们可以为回调函数添加返回值的原因。