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等等都是以这个类为基础。

 

 

 

 

 

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

相关推荐

Android dex文件反编译为Java源码

工具准备 dex2jar:国人写的一个dex反编译为java的工具jd-gui:查看java源码的gui工具反编译步骤步骤一把test_apk-debug.apk里的classes.dex转换为test_apk-debug_dex2jar.jard2j-dex2jar.sh -f -o output_jar.jar apk_to_decompile.apk

[译]代码优先的Java 9模块系统教程(一)

Java平台模块系统(JPMS)将模块化带入Java和JVM,并改变了我们在大型应用中的编程方式。 为了充分利用它,我们需要很好地了解,第一步是学习基础知识。 在本教程中,我首先向你展示一个简单的Hello World示例,然后我们用Java 9将一个现有的demo程序模块化。我们将创建模块声明(module-info。java),使用模块路径来编译,打包,

Java源码分析:产生随机数Random与ThreadLocalRandom的区别

Java用于产生随机数的方法主要有两种:java.util.Random和java.util.concurrent.ThreadLocalRandom。Random从Jdk 1.0开始就有了,而ThreadLocalRandom是Jdk1.7才新增的。简单从命名和类所在的包上看,两者的区别在于对并发的支持。RandomRandom是一个伪随机数生成器,它内置了一个种子数seed。获取随机

雪花算法实现-分布式系统

一、订单id的特殊性订单数据非常庞大,将来一定会做分库分表。那么这种情况下, 要保证id的唯一,就不能靠数据库自增,而是自己来实现算法,生成唯一id。二、雪花算法这里的订单id是通过一个工具类生成的,而工具类所采用的生成id算法,是由Twitter公司开源的snowflake(雪花)算法。三、简单原理雪花算法会生成一个64位的二进制数据,为一个Long型。(转换成字符串后长度最多19位) ,其基本

Linux查看及修改系统的资源限制命令ulimit

在Linux,查看系统对资源使用的显示可以使用命令ulimit,其中参数-a会列出所有的资源使用限制。[demo@server ~]$ ulimit -acore file size (blocks, -c) 0data seg size (kbytes, -d) unlimitedscheduling priority (-e) 0f

JavaScript 点击事件只执行一次的方法

JavaScript要实现只允许Button被点击执行一次的办法有几种:方法一、在添加事件监听器时,设置选项once为true示例:<div id="btn0"> My button</div><script> function doSomething(v) { console.log(v); } document.getE

JIT的分层编译和逃逸分析

JIT到底在Java的运行中发挥了什么作用呢?根据查阅到的资料,一个作用是做分层编译,一个是做对象的逃逸分析。对于循环体中的代码,循环到一定的程度的时候,就会被再次被编译,编程执行速度更加迅速的代码。对于新建的对象,讲过逃逸分析,如果数据不会逃逸,则将数据放在栈上,不再在heap上新建这个对象。这样的好处是:避免了在堆上新建的锁堆导致的资源损耗不需要GC

CKEditor5事件系统(基础使用)

最近在学习CK5,一种最大的感受就是CK5的架构不是很大,但是内容特别多。笔者在学习中,总结出一个浅显的道理,那就是掌握基础知识,对框架宏观把握,学习起来会事半功倍。今天开始初步研究一下CK5的事件系统:在CK5的事件系统中,关键的一个对象被称作Emitter(发射器),Emitter是一个可以发送事件的对象。如何创建一个Emitter,下面的代码创建了一个混合了事件发送的AnyClass类,它实

CKEditor5事件系统(事件优先级)

今天继续学习CK5的事件系统,上一节我们知道了绑定和取消绑定事件的两种方法,知道在一个emitter上一个同名事件可以绑定多个回调函数,自然问题来了,这些函数的执行顺序是怎么样的呢?CK5的事件监听优先级实际上,对于一个同名事件,CK5提供了事件优先级功能,如下代码所示const anyClass = new AnyClass(); anyClass.on( 'eventName', ( even

CKEditor5事件系统(代理事件)

emitter接口提供了事件代理机制。也就是说指定选择的事件能够被其他的emitter触发。 1、代理指定的事件到另一个emitterlet anyClass = new AnyClass(); let anotherClass = new AnyClass(); let oneClass = new AnyClass(); anotherClass.on('bar',(evt,data