美文网首页
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