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

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

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

上一节我们分析了CK5的EmitterMixin类的重点方法addEventListener,今天我们再认真看看fire方法。

fire( eventOrInfo, ...args ) {
	try {
		const eventInfo = eventOrInfo instanceof EventInfo ? eventOrInfo : new EventInfo( this, eventOrInfo );
		const event = eventInfo.name;
		let callbacks = getCallbacksForEvent( this, event );

		// Record that the event passed this emitter on its path.
		eventInfo.path.push( this ); 

		// Handle event listener callbacks first.
		if ( callbacks ) {
			// Arguments passed to each callback.
			const callbackArgs = [ eventInfo, ...args ];

			// Copying callbacks array is the easiest and most secure way of preventing infinite loops, when event callbacks
			// are added while processing other callbacks. Previous solution involved adding counters (unique ids) but
			// failed if callbacks were added to the queue before currently processed callback.
			// If this proves to be too inefficient, another method is to change `.on()` so callbacks are stored if same
			// event is currently processed. Then, `.fire()` at the end, would have to add all stored events.
			callbacks = Array.from( callbacks );

			for ( let i = 0; i < callbacks.length; i++ ) {
				callbacks[ i ].callback.apply( this, callbackArgs );

				// Remove the callback from future requests if off() has been called.
				if ( eventInfo.off.called ) {
					// Remove the called mark for the next calls.
					delete eventInfo.off.called;
					this._removeEventListener( event, callbacks[ i ].callback );
				}

				// Do not execute next callbacks if stop() was called.
				if ( eventInfo.stop.called ) {
					break;
				}
			}
		}

		// Delegate event to other emitters if needed.
		if ( this._delegations ) {
			const destinations = this._delegations.get( event );
			const passAllDestinations = this._delegations.get( '*' );

			if ( destinations ) {
				fireDelegatedEvents( destinations, eventInfo, args );
			}
			if ( passAllDestinations ) {
				fireDelegatedEvents( passAllDestinations, eventInfo, args );
			}
		}

		return eventInfo.return;
	} catch ( err ) {
		// @if CK_DEBUG // throw err;
		/* istanbul ignore next */
		CKEditorError.rethrowUnexpectedError( err, this );
	}
},

重点方法

这里面有一个重点方法:

// Get the list of callbacks for a given event, but only if there any callbacks have been registered.
// If there are no callbacks registered for given event, it checks if this is a specific event and looks
// for callbacks for it's more generic version.
function getCallbacksForEvent( source, eventName ) {
	let event;

	if ( !source._events || !( event = source._events[ eventName ] ) || !event.callbacks.length ) {
		// There are no callbacks registered for specified eventName.
		// But this could be a specific-type event that is in a namespace.
		if ( eventName.indexOf( ':' ) > -1 ) {
			// If the eventName is specific, try to find callback lists for more generic event.
			return getCallbacksForEvent( source, eventName.substr( 0, eventName.lastIndexOf( ':' ) ) );
		} else {
			// If this is a top-level generic event, return null;
			return null;
		}
	}

	return event.callbacks;
}

注意,这里获取指定事件的回调函数列表的时候,举个例子来说吧,如果指定的事件名称是insert:p;它会先寻找这个事件对应的回调函数列表,如果不存在,则会寻找insert事件名称对应的回调函数;因此当我们调用emitters.fire('insert:p', args)的时候,监听insert:p 事件和监听insert事件的回调函数都会执行。

这里有一个疑问?如果这个回去回调函数列表的方法已经注册了insert:p事件,那么它就不会往下寻找更通用的insert事件,这个又是怎么出发insert事件的回调函数呢?这里留一个疑问哈。

当我们获取回调函数列表后,会构建EventInfo对象,然后将当前的emitter放到EventInfo的path路径中,这样我们就方便知道事件的传播路径啦。 

剩下的自然就是迭代回调函数,然后执行。在执行的过程中,我们看到一些逻辑,如果这个事件调用了off方法,那么这个回调函数将取消监听;而如果这个事件调用了stop()方法,那么回调函数的迭代将结束;这就是有的时候我们需要用自定义的回调屏蔽掉一些不必要的回调的时候,我们就调用event.stop()即可。

剩下的部分代码是关于事件代理的,这个我们以后在分析,当这些迭代执行完成后,我们返回eventInfo.return属性,这就是我们可以为回调函数添加返回值的原因。

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

相关推荐

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模块系统教程(二)

服务监控(ServiceMonitor)让我们来想象一个提供娱乐服务的网络,可能是社交网络或者是视频网络。我们希望监控这些服务,以确定系统的健康状况,并且在发生问题时能够发现,而不是客户报告。 这就是ServiceMonitor示例程序所要做的:监视这些服务(另一个惊喜)。幸运的是,服务已经收集了我们想要的数据,ServiceMonitor所需要做的就是定期查询。 不幸的是,并

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

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

CKEditor5——模型理解(二:Node)

上一节我们理解了基本的CK5的模型基本信息,今天我们来学习一些模型的API。节点说明首先,需要理解的就是模型的节点。在这一点上,CK5的模型节点和dom的节点有点类似,也有一些不同。我会在文章中一一介绍。节点是模型树的基本结构。它是模型中不同节点类型的一种抽象。这里需要指出的一点是:如果一个节点从模型树中分离出来,你可以使用它的 API 来操作它。但是,非常重要的是,已经附加到模型树的节点只能通过