美文网首页ckeditor5
ckeditor5/utils:emittermixin(事件监

ckeditor5/utils:emittermixin(事件监

作者: videring | 来源:发表于2020-09-23 01:19 被阅读0次
    ckeditor5/utils:emittermixin主要负责事件绑定、触发和代理用。主要方法有listenTo/stopListeningfiredelegate/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 = {} )

    onceon方法依赖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:somepropchange的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;
    

    相关文章

      网友评论

        本文标题:ckeditor5/utils:emittermixin(事件监

        本文链接:https://www.haomeiwen.com/subject/uqovyktx.html