ckeditor5/utils:emittermixin(事件监
2020-09-23 本文已影响0人
videring
ckeditor5/utils:emittermixin主要负责事件绑定、触发和代理用。主要方法有listenTo/stopListening、fire和delegate/stopDelegating。一般而言,EmitterMixin会extend到ObservableMixin,而ObservableMixin最后会mix到某个class:
// class
import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
import mix from '@ckeditor/ckeditor5-utils/src/mix';
class AnyClass {
// ...
}
mix( AnyClass, ObservableMixin );
// observableMixin.js
import { extend, isObject } from 'lodash-es';
extend( ObservableMixin, EmitterMixin );
零、_listeningTo和_emitterId
emittermixin有两个常量_listeningTo和_emitterId,作用如下:
// contains a list of emitters that this object is listening to.
// This list has the following format:
// _listeningTo: {
// emitterId: {
// emitter: emitter,
// callbacks: {
// event1: [ callback1, callback2, ... ]
// ....
// }
// },
// ...
// }
const _listeningTo = Symbol( 'listeningTo' );
// emitter's unique id which can be set only once.
const _emitterId = Symbol( 'emitterId' );
一、listenTo( emitter, event, callback, options = {} )
once和on方法依赖listenTo方法,所以只需知道listenTo的实现即可。
-
emitters保存当前实例下_listeningTo常量对应的值,默认为{} -
emitterId保存emitter[Symbol( 'emitterId' )] - 判断
emitters[ emitterId ]是否存在,否则设置emitters[ emitterId ];判断emitters[ emitterId ].callbacks[ event ]是否存在,否则设置emitters[ emitterId ].callbacks[ event ],并讲当前event塞入进去
if ( !( emitterInfo = emitters[ emitterId ] ) ) {
emitterInfo = emitters[ emitterId ] = {
emitter,
callbacks: {}
};
}
if ( !( eventCallbacks = emitterInfo.callbacks[ event ] ) ) {
eventCallbacks = emitterInfo.callbacks[ event ] = [];
}
- 通过
createEventNamespace方法,创建一个generic-specific事件关系体系,如"change:someprop"事件的处理,其往其callback数组塞入"change"回调函数;同时会设置emitter._events为空对象,然后emitter._events['change:someprop']设置成{ callbacks: [], childEvents: []}形式,其中会往callbacks中塞入emitter._events['change']的callback数组元素,会往emitter._events['change']的childEvents中塞入"change:someprop"事件名 - 通过
getCallbacksListsForNamespace方法,获取上例中change:someprop和change的callbacks属性(数组),保存在list变量 - 遍历
list变量,根据优先级,选择是否往元素中插入形如{callback, priority}的数据。(callback是listenTo的参数)
二、stopListening( emitter, event, callback )
off依赖stopListening,所以只需知道stopListening的实现即可。这个功能相对简单:
- 如果
callback存在,那么 - 否则,如果
this[ _listeningTo ][ emitter[ Symbol( 'emitterId' ) ] ].callbacks[ event ]存在,那么 - 否则,如果
this[ _listeningTo ][ emitter[ Symbol( 'emitterId' ) ] ]存在,那么停止当下实例所有的事件 - 否则,停掉
this[ _listeningTo ]下所有的事件。
其中_listeningTo为Symbol( 'listeningTo' )
三、delegate
先看一个test用例(详见tests/emittermixin.js):
it( 'supports delegation under a different name', () => {
const emitterA = getEmitterInstance();
const emitterB = getEmitterInstance();
const emitterC = getEmitterInstance();
const emitterD = getEmitterInstance();
const spyAFoo = sinon.spy();
const spyABar = sinon.spy();
const spyCBaz = sinon.spy();
const spyDFoo = sinon.spy();
emitterB.delegate( 'foo' ).to( emitterA, 'bar' );
emitterB.delegate( 'foo' ).to( emitterC, name => name + '-baz' );
emitterB.delegate( 'foo' ).to( emitterD );
emitterA.on( 'foo', spyAFoo );
emitterA.on( 'bar', spyABar );
emitterC.on( 'foo-baz', spyCBaz );
emitterD.on( 'foo', spyDFoo );
emitterB.fire( 'foo' );
sinon.assert.calledOnce( spyABar );
sinon.assert.calledOnce( spyCBaz );
sinon.assert.calledOnce( spyDFoo );
sinon.assert.notCalled( spyAFoo );
} );
delegate方法会返回一个包含to方法的对象:
delegate( ...events ) {
return {
to: ( emitter, nameOrFunction ) => {
if ( !this._delegations ) {
this._delegations = new Map();
}
// Originally there was a for..of loop which unfortunately caused an error in Babel that didn't allow
// build an application. See: https://github.com/ckeditor/ckeditor5-react/issues/40.
events.forEach( eventName => {
const destinations = this._delegations.get( eventName );
if ( !destinations ) {
this._delegations.set( eventName, new Map( [ [ emitter, nameOrFunction ] ] ) );
} else {
destinations.set( emitter, nameOrFunction );
}
} );
}
};
},
实现也很简单,主要是要往this._delegations中保存类似{${event} : { ${emitter}: ${nameOrFunction} }}的结构,会在stopDelegating中用到。
四、stopDelegating
stopDelegating( event, emitter ) {
if ( !this._delegations ) {
return;
}
if ( !event ) {
this._delegations.clear();
} else if ( !emitter ) {
this._delegations.delete( event );
} else {
const destinations = this._delegations.get( event );
if ( destinations ) {
destinations.delete( emitter );
}
}
}
五、fire( eventOrInfo, ...args )
- 获取所有跟eventOrInfo相关的callbacks进行遍历,重复执行下面的动作:执行回调函数callback,如果callback中有执行过
off方法(如once的回调方法中会执行event.off(),eventOrInfo.off.called会被设置成true),那么就删除eventInfo.off.called,并从事件相关的callbacks中删除正在执行的callback;如果callback中有执行过stop方法,那么就会从循环中break,停止遍历。 - 如果有代理(
Delegate,即存在this._delegations),触发fireDelegatedEvents方法,该方法内部会触发代理方emitter的fire方法。
if ( this._delegations ) {
const destinations = this._delegations.get( event );
// emitterB.delegate( '*' ).to( emitterA ); 全部代理,emitterB上fire任意时间,emitterA的on方法的回调函数都是触发。
const passAllDestinations = this._delegations.get( '*' );
if ( destinations ) {
fireDelegatedEvents( destinations, eventInfo, args );
}
if ( passAllDestinations ) {
fireDelegatedEvents( passAllDestinations, eventInfo, args );
}
}
- 返回return值
return eventInfo.return;