敲碎时间,铸造不朽的个人专栏
上一篇

CKEditor5事件系统源码分析(一)

广告
选中文字可对指定文章内容进行评论啦,→和←可快速切换按钮,绿色背景文字可以点击查看评论额。
大纲

在学习CK5的时候,在事件系统这一块,有一个特别关键的类,那就是EmitterMixin这个类,这个类在什么地方呢?他其实就在ckeditor5-utils包中。下面我们来看看这个类:  

EmitterMixin类

const EmitterMixin = {
	/**
	 * @inheritDoc
	 */
	on( event, callback, options = {} ) {
		this.listenTo( this, event, callback, options );
	},
	/**
	 * @inheritDoc
	 */
	listenTo( emitter, event, callback, options = {} ) {
		let emitterInfo, eventCallbacks;

		// _listeningTo 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, ... ]
		//             ....
		//         }
		//     },
		//     ...
		// } 

		if ( !this[ _listeningTo ] ) {
			this[ _listeningTo ] = {};
		}

		const emitters = this[ _listeningTo ];

		if ( !_getEmitterId( emitter ) ) {
			_setEmitterId( emitter );
		}

		const emitterId = _getEmitterId( emitter );

		if ( !( emitterInfo = emitters[ emitterId ] ) ) {
			emitterInfo = emitters[ emitterId ] = {
				emitter,
				callbacks: {}
			};
		}

		if ( !( eventCallbacks = emitterInfo.callbacks[ event ] ) ) {
			eventCallbacks = emitterInfo.callbacks[ event ] = [];
		}

		eventCallbacks.push( callback );

		// Finally register the callback to the event.
		addEventListener( this, emitter, event, callback, options );
	},
	/**
	 * @inheritDoc
	 */
	_addEventListener( event, callback, options ) {
		createEventNamespace( this, event );

		const lists = getCallbacksListsForNamespace( this, event );
		const priority = priorities.get( options.priority );

		const callbackDefinition = {
			callback,
			priority
		};

		// Add the callback to all callbacks list.
		for ( const callbacks of lists ) {
			// Add the callback to the list in the right priority position.
			insertToPriorityArray( callbacks, callbackDefinition );
		}
	},
}
/**
 * Sets emitter's unique id.
 *
 * **Note:** `_emitterId` can be set only once.
 *
 * @protected
 * @param {module:utils/emittermixin~Emitter} emitter An emitter for which id will be set.
 * @param {String} [id] Unique id to set. If not passed, random unique id will be set.
 */
export function _setEmitterId( emitter, id ) {
	if ( !emitter[ _emitterId ] ) {
		emitter[ _emitterId ] = id || uid();
	}
}

/**
 * Returns emitter's unique id.
 *
 * @protected
 * @param {module:utils/emittermixin~Emitter} emitter An emitter which id will be returned.
 */
export function _getEmitterId( emitter ) {
	return emitter[ _emitterId ];
}
// Helper for registering event callback on the emitter.
function addEventListener( listener, emitter, event, callback, options ) {
	if ( emitter._addEventListener ) {
		emitter._addEventListener( event, callback, options );
	} else {
		// Allow listening on objects that do not implement Emitter interface.
		// This is needed in some tests that are using mocks instead of the real objects with EmitterMixin mixed.
		listener._addEventListener.call( emitter, event, callback, options );
	}
}

以上部分,我贴出了这个类最核心的代码,总结如下:

1、on方法实际上是listenTo方法的一个包装,on方法监听的是emitter自身,而listenTo可以监听别的emitter。

2、listenTo有四个参数,分别是需要监听的emitter,监听的事件名称,对应的回调函数,以及可选参数,比如优先级啥的。

3、这个方法调用的结果是会构造一个emitters数组。这个数组的结构如下:

_listeningTo: {
	emitterId: {
	    emitter: emitter,
		callbacks: {
			event1: [ callback1, callback2, ... ]
		    ....
		}
    },
	...
}

结果是会创建多个emitter,每个emitter会有一个唯一的emitterId,这个id对应的属性如下:emitter,一组callbacks,这个callbacks实际上是一个map,key对应的是事件名称,而value对应的是这个事件对应的回调函数数组。

/** _listeningTo这个属性对应的命名空间不存在,创建这个属性 */
if ( !this[ _listeningTo ] ) {
	this[ _listeningTo ] = {};
}

/** 获取_listeningTo属性下的emitters*/
const emitters = this[ _listeningTo ];

/** 如果传递进来的emitters没有一个emitterId,那么设置一个id */
if ( !_getEmitterId( emitter ) ) {
	_setEmitterId( emitter );
}
/** 获取emitterId*/
const emitterId = _getEmitterId( emitter );

/** 构建emitter基本信息,实际上就是设置emitter属性和callbacks属性 */
if ( !( emitterInfo = emitters[ emitterId ] ) ) {
	emitterInfo = emitters[ emitterId ] = {
		emitter,
		callbacks: {}
	};
}
/** 判断事件名称event有没有对应的回调函数数组,没有的话创建一个,有的话将回调函数添加到末尾 */
if ( !( eventCallbacks = emitterInfo.callbacks[ event ] ) ) {
	eventCallbacks = emitterInfo.callbacks[ event ] = [];
}
eventCallbacks.push( callback );

addEventListener

最后一步就是绑定事件addEventListener( this, emitter, event, callback, options );

下面我们介绍绑定事件:

这个方法实际上调用的是emitter类中的_addEventListener( event, callback, options )

createEventNamespace( this, event );

const lists = getCallbacksListsForNamespace( this, event );
const priority = priorities.get( options.priority );

const callbackDefinition = {
	callback,
	priority
};

// Add the callback to all callbacks list.
for ( const callbacks of lists ) {
	// Add the callback to the list in the right priority position.
	insertToPriorityArray( callbacks, callbackDefinition );
}

这个类的作用如下:

1、创建事件的命名空间

2、返回名称为event的事件对应的回调函数数组

3、根据参数options中的优先级对回调函数数组排序,这样我们在执行fire的时候就能够根据优先级先后执行回调函数啦。

 

注意:在ck5中事件有一个命名空间的概念,它的实现其实就是在createEventNamespace( this, event )

这个函数里实现的,如果想理解具体的请参考这个类的具体实现,我在这里不做分析啦。

另一个实现其实就是对回调函数按照优先级进行排序。这里就这两个知识点。

 

有了以上的分析,我们可以得出结论就是事件系统的emitters的基本结构和事件绑定已经构建起来,下一步就是fire出发回调函数的执行,我们以后在分析。欢迎分析和讨论交流。

这个类可以说的整个ck5中最基础,最关键的一个类,后面的Observavle,Plugin,Command,Editor,Model,Document等等都是以这个类为基础。

 

 

 

 

 

版权声明:著作权归作者所有。

X

欢迎加群学习交流

联系我们