CKEditor5——模型理解(四:模型组成)

百科知识2025-04-261

'insertContent', 'deleteContent', 'modifySelection''insertContent', 'deleteContent', 'modifySelection', 'getSelectedContent', 'applyOperation'今天我们来深入学习一下CK5的模型。

我们先看看model.js的源码:

xport default class Model {
    constructor() {

        this.markers = new MarkerCollection();

        this.document = new Document( this );

        this.schema = new Schema();

        this._pendingChanges = [];

        this._currentWriter = null;

        [ 'insertContent', 'deleteContent', 'modifySelection', 'getSelectedContent', 'applyOperation' ]
            .forEach( methodName => this.decorate( methodName ) );

        this.on( 'applyOperation', ( evt, args ) => {
            const operation = args[ 0 ];

            operation._validate();
        }, { priority: 'highest' } );

        // Register some default abstract entities.
        this.schema.register( '$root', {
            isLimit: true
        } );

        this.schema.register( '$block', {
            allowIn: '$root',
            isBlock: true
        } );

        this.schema.register( '$text', {
            allowIn: '$block',
            isInline: true,
            isContent: true
        } );

        this.schema.register( '$clipboardHolder', {
            allowContentOf: '$root',
            allowChildren: '$text',
            isLimit: true
        } );

        this.schema.register( '$documentFragment', {
            allowContentOf: '$root',
            allowChildren: '$text',
            isLimit: true
        } );

        this.schema.register( '$marker' );
        this.schema.addChildCheck( ( context, childDefinition ) => {
            if ( childDefinition.name === '$marker' ) {
                return true;
            }
        } );

        injectSelectionPostFixer( this );

        this.document.registerPostFixer( autoParagraphEmptyRoots );

    }
}

从上面的源码,我们可以看出。模型部分包含的功能比较多,主要的有一下几点:

1、一个存储marker的集合

2、一个模型文档属性。比如。主要是ck的模型数据

3、一个存储schema的属性,并且定义一些基本元素,比如$root,$block,$text,$clipboardHolder,$documentFragment,$marker

4、一个存储模型变化操作的回调函数。

5、一个用于操作修改模型的writer

6、装饰一些可以在外部监听的方法:比如'insertContent', 'deleteContent', 'modifySelection', 'getSelectedContent', 'applyOperation'等

7、注册了一个模型后处理器。

8、绑定了一个监听applyOperation事件的监听函数,用于验证操作的合法性。

在这里,我们重点分析一下:model.change(callback)这个方法:

change( callback ) {
        try {
            if ( this._pendingChanges.length === 0 ) {

                this._pendingChanges.push( { batch: new Batch(), callback } );
                return this._runPendingChanges()[ 0 ];
            } else {

                return callback( this._currentWriter );
            }
        } catch ( err ) {
            CKEditorError.rethrowUnexpectedError( err, this );
        }
    }

_runPendingChanges() {
        const ret = [];

        this.fire( '_beforeChanges' );

        while ( this._pendingChanges.length ) {

            const currentBatch = this._pendingChanges[ 0 ].batch;
            this._currentWriter = new Writer( this, currentBatch );

            const callbackReturnValue = this._pendingChanges[ 0 ].callback( this._currentWriter );
            ret.push( callbackReturnValue );

            this.document._handleChangeBlock( this._currentWriter );

            this._pendingChanges.shift();
            this._currentWriter = null;
        }

        this.fire( '_afterChanges' );

        return ret;
    }
//举个例子
model.change( writer => {
     writer.insertText( 'foo', paragraph, 'end' ); // foo.

     model.change( writer => {
        writer.insertText( 'bar', paragraph, 'end' ); // foobar.
     } );

      writer.insertText( 'bom', paragraph, 'end' ); // foobarbom.
} );

可以看到:在例子中,外层调用的时候,this._pendingChanges为空,这个时候会执行

this._pendingChanges.push( { batch: new Batch(), callback } );
return this._runPendingChanges()[ 0 ];

这时,会创建一个叫做Batch的对象,同时运行一个叫做_runPendingChanges()的函数。

这个函数的逻辑就是创建一个模型writer来处理模型文档块改变的业务逻辑。同时还触发了两个事件_beforeChanges和_afterChanges,注意,这个writer的batch属性是最外层调用时候创建的,因此内层的函数调用时候使用的这个writer将共享这个Batch,因此,外层和内层实际上是用一个Batch。当调用结束以后,这个this._pendingChanges会被移除掉。

 

我们再看看另一个方法:enqueueChange([ batchOrType ], callback)

enqueueChange( batchOrType, callback ) {
    try {
        if ( typeof batchOrType === 'string' ) {
            batchOrType = new Batch( batchOrType );
        } else if ( typeof batchOrType == 'function' ) {
            callback = batchOrType;
            batchOrType = new Batch();
        }

        this._pendingChanges.push( { batch: batchOrType, callback } );

        if ( this._pendingChanges.length == 1 ) {
            this._runPendingChanges();
        }
    } catch ( err ) {
        // @if CK_DEBUG // throw err;
        /* istanbul ignore next */
        CKEditorError.rethrowUnexpectedError( err, this );
    }
}

可以看到,这个方法多了一个参数就是batchOrType,这个参数可能是string,或者Batch类型,当属于没有参数,或者参数类型为string时,我用下面的例子说明

model.change( writer => {
    console.log( 1 );

    model.enqueueChange( writer => {
        console.log( 2 );
    } );

    console.log( 3 );
} ); // Will log: 1, 3, 2.

从上面的代码可以看出,只有在this._pendingChanges == 1时,才会执行enqueueChanges()的回调函数,实际上上面的逻辑就是最后一个执行,所以以上代码会最后打印出2。

当这个参数是从最外层的调用而来的时候,此时这个回调函数将共享这个batch,实际上就是起到了自己将定义的操作放到某个Batch的作用。

好了,今天分享了模型的相关属性和修改模型的方法原理,欢迎分享讨论。