美文网首页
ckeditor5/utils:ObservableMixin

ckeditor5/utils:ObservableMixin

作者: videring | 来源:发表于2020-09-25 11:37 被阅读0次

    ckeditor5/utils:如何设置observable讲到,如何设置observable,其中的核心就是ObservableMixin。这里聊聊ObservableMixin的主要方法:setbindbindTodecorate

    const ObservableMixin = {
        set( name, value ) {},
        bind( ...bindProperties )  {},
        unbind( ...unbindProperties ) {},
        decorate( methodName ) {},
    }
    extend( ObservableMixin, EmitterMixin )
    

    使用示例:

    A.bind( 'a', 'b', 'c' ).to( B, 'x', 'y', 'x' );
    A.bind( 'd' ).to( B, 'z' ).to( C, 'w' ).as( callback );
    button.bind( 'isEnabled' ).toMany( commands, 'isEnabled', ( isAEnabled, isBEnabled, isCEnabled ) => {
        return isAEnabled && isBEnabled && isCEnabled;
    } );
    

    extend( ObservableMixin, EmitterMixin )

    extend的作用就是将EmitterMixin(见ckeditor5/utils:emittermixin(事件监听机制))关于事件触发、监听和代理等方法引入有到ObservableMixin中。


    首先,定义了三个变量:

    const observablePropertiesSymbol = Symbol( 'observableProperties' );
    const boundObservablesSymbol = Symbol( 'boundObservables' );
    const boundPropertiesSymbol = Symbol( 'boundProperties' );
    

    set

    • initObservable( this ):依据上面的三个常量,往当前实例(this,一般是指混入了ObservableMixin的class实例)上挂上三个属性,其值都是Map结构,如果已设置过observablePropertiesSymbol,则退出,避免重复赋值:
    Object.defineProperty( observable, observablePropertiesSymbol, {
        value: new Map()
    } );
    //      A.bind( 'a', 'b', 'c' ).to( B, 'x', 'y', 'x' );
    //      console.log( A._boundObservables );
    //
    //          Map( {
    //              B: {
    //                  x: Set( [
    //                      { observable: A, property: 'a', to: [ [ B, 'x' ] ] },
    //                      { observable: A, property: 'c', to: [ [ B, 'x' ] ] }
    //                  ] ),
    //                  y: Set( [
    //                      { observable: A, property: 'b', to: [ [ B, 'y' ] ] },
    //                  ] )
    //              }
    //          } )
    //
    //      A.bind( 'd' ).to( B, 'z' ).to( C, 'w' ).as( callback );
    //      console.log( A._boundObservables );
    //
    //          Map( {
    //              B: {
    //                  x: Set( [
    //                      { observable: A, property: 'a', to: [ [ B, 'x' ] ] },
    //                      { observable: A, property: 'c', to: [ [ B, 'x' ] ] }
    //                  ] ),
    //                  y: Set( [
    //                      { observable: A, property: 'b', to: [ [ B, 'y' ] ] },
    //                  ] ),
    //                  z: Set( [
    //                      { observable: A, property: 'd', to: [ [ B, 'z' ], [ C, 'w' ] ], callback: callback }
    //                  ] )
    //              },
    //              C: {
    //                  w: Set( [
    //                      { observable: A, property: 'd', to: [ [ B, 'z' ], [ C, 'w' ] ], callback: callback }
    //                  ] )
    //              }
    //          } )
    Object.defineProperty( observable, boundObservablesSymbol, {
        value: new Map()
    } );
    //      A.bind( 'a', 'b', 'c' ).to( B, 'x', 'y', 'x' );
    //      console.log( A._boundProperties );
    //
    //          Map( {
    //              a: { observable: A, property: 'a', to: [ [ B, 'x' ] ] },
    //              b: { observable: A, property: 'b', to: [ [ B, 'y' ] ] },
    //              c: { observable: A, property: 'c', to: [ [ B, 'x' ] ] }
    //          } )
    //
    //      A.bind( 'd' ).to( B, 'z' ).to( C, 'w' ).as( callback );
    //      console.log( A._boundProperties );
    //
    //          Map( {
    //              a: { observable: A, property: 'a', to: [ [ B, 'x' ] ] },
    //              b: { observable: A, property: 'b', to: [ [ B, 'y' ] ] },
    //              c: { observable: A, property: 'c', to: [ [ B, 'x' ] ] },
    //              d: { observable: A, property: 'd', to: [ [ B, 'z' ], [ C, 'w' ] ], callback: callback }
    //          } )
    Object.defineProperty( observable, boundPropertiesSymbol, {
        value: new Map()
    } );
    
    • 通过observablePropertiesSymbol来返回和fire出setchange两个事件
    const properties = this[ observablePropertiesSymbol ];
    Object.defineProperty( this, name, {
        enumerable: true,
        configurable: true,
        get() {
            return properties.get( name );
        },
        set( value ) {
            const oldValue = properties.get( name );
            // Fire `set` event before the new value will be set to make it possible
            // to override observable property without affecting `change` event.
            // See https://github.com/ckeditor/ckeditor5-utils/issues/171.
            let newValue = this.fire( 'set:' + name, name, value, oldValue );
            if ( newValue === undefined ) {
                newValue = value;
            }
            // Allow undefined as an initial value like A.define( 'x', undefined ) (#132).
            // Note: When properties map has no such own property, then its value is undefined.
            if ( oldValue !== newValue || !properties.has( name ) ) {
                properties.set( name, newValue );
                this.fire( 'change:' + name, name, newValue, oldValue );
            }
        }
    } );
    
    • 最后,通过赋值触发上一步Object.defineProperty中的set方法:
    set( name, value ) {
      // initObservable
      // Object.defineProperty( this, name, {
      //  get
      //  set  
      //})
      this[ name ] = value
    }
    

    bind

    逻辑不复杂,代码简化如下:

    bind( ...bindProperties ) {
        initObservable( this );
        const boundProperties = this[ boundPropertiesSymbol ];
        bindProperties.forEach( propertyName => {
            if ( boundProperties.has( propertyName ) ) {
                /**
                 * Cannot bind the same property more than once.
                 *
                 * @error observable-bind-rebind
                 */
                throw new CKEditorError( 'observable-bind-rebind', this );
            }
        } );
        const bindings = new Map();
        // @typedef {Object} Binding
        // @property {Array} property Property which is bound.
        // @property {Array} to Array of observable–property components of the binding (`{     observable: ..., property: .. }`).
        // @property {Array} callback A function which processes `to` components.
        bindProperties.forEach( a => {
            const binding = { property: a, to: [] };
            boundProperties.set( a, binding );
            bindings.set( a, binding );
        } );
        return {
            to: bindTo,
            toMany: bindToMany,
            _observable: this,
            _bindProperties: bindProperties,
            _to: [],
            _bindings: bindings
        };
    }
    

    主要逻辑并不复杂,先是跟上一节一样调用initObservable初始化三个变量,最后对参数bindProperties数组(一个元素或多个属性)依次进行设置,设置格式大致如下:

    //          Map( {
    //                  a: Set( [
    //                      { property: 'a', to: [] }
    //                  ] )
    //          } )
    

    这种数据格式必然在一下步to方法中会使用到。

    bindTo

    A.bind( 'a', 'b', 'c' ).to( B, 'x', 'y', 'x' );to方法会调用bindTo方法,主要逻辑如下:

    • to方法的参数转换成格式化的数据,保存在parsedArgs常量里:
    {
      to:  [
        {
            observable: B, 
            properties: ['x', 'y', 'x'],
            callback: callback //如果有的话
        }
      ]
    }
    
    • 在当前上下文A实例中,注册对B实例的change回调;
    // observable即为A实例,to.observable即为B实例
    observable.listenTo( to.observable, 'change', ( evt, propertyName ) => {
        bindings = boundObservables.get( to.observable )[ propertyName ];
        // Note: to.observable will fire for any property change, react
        // to changes of properties which are bound only.
            // 此处bindings就是下一步中第2小步的数据
        if ( bindings ) {
            bindings.forEach( binding => {
                updateBoundObservableProperty( observable, binding.property );
            } );
        }
    } );
    

    其中,updateBoundObservableProperty方法主要作用是将B实例中绑定属性x的值赋给A实例的a属性。

    • 更新this[boundObservablesSymbol]
      1.在上一节中bind方法中有一个_bindings,其形式如{property: 'a', to: []},在这里会调用updateBindToBound方法,往to空数组中塞入数据,to数组形如[[B, 'x']]
      2.在当前上下文下,也就是A实例中,往this[boundObservablesSymbol]的值中塞入如下格式的数据:
    x: Set( [
       {property: 'a', to: [ [ B, 'x' ] ]}
    ])
    

    通过上面几步,bindTo方法实现了:
    1.注册监听方法,实现了对绑定对象指定属性的监听;
    2.更新当前实例下boundObservablesSymbol属性值的设置;

    decorate

    decorate逻辑比较简单,见ckeditor5/utils:如何设置observable的decorate(装饰)小节。

    decorate( methodName ) {
        const originalMethod = this[ methodName ];
        if ( !originalMethod ) {
            throw new CKEditorError(
                'observablemixin-cannot-decorate-undefined',
                this,
                { object: this, methodName }
            );
        }
        this.on( methodName, ( evt, args ) => {
            evt.return = originalMethod.apply( this, args );
        } );
        this[ methodName ] = function( ...args ) {
            return this.fire( methodName, args );
        };
    }
    

    相关文章

      网友评论

          本文标题:ckeditor5/utils:ObservableMixin

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