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;