在ckeditor5/utils:如何设置observable讲到,如何设置observable,其中的核心就是ObservableMixin
。这里聊聊ObservableMixin
的主要方法:set
、bind
、bindTo
、decorate
。
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出set
和change
两个事件
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 );
};
}
网友评论