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

CKEditor5——Uitls(DomEmitterMixin类理解)

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

CKEditor5——Uitls(DomEmitterMixin类理解)

今天开始研究在CK5的Utils包中的另一个比较重要的类,这个类就是DomEmitterMixin,我们先看看这个类的代码:

const DomEmitterMixin = extend( {}, EmitterMixin, {
	listenTo( emitter, event, callback, options = {} ){
	},
	stopListening(){
	},
	_getProxyEmitter() {
	},
	_getAllProxyEmitters(){
	}
}

通过上面的代码,我们不难看出,这个类是继承字基础类EmitterMixin,然后扩展并且重写了基类的两个方法,它们分别是:listenTo和stopListening。思考一下,这在设计模式中是什么模式呢?是不是就是装饰器模式。如果猜测没错的话,这个装饰器增强的功能就是为dom节点绑定事件和删除事件。接下来,我们分析关键的方法listenTo

listenTo( emitter, event, callback, options = {} ) {
	// Check if emitter is an instance of DOM Node. If so, use corresponding ProxyEmitter (or create one if not existing).
	if ( isNode( emitter ) || isWindow( emitter ) ) {
		const proxyOptions = {
			capture: !!options.useCapture,
			passive: !!options.usePassive
		};

		const proxyEmitter = this._getProxyEmitter( emitter, proxyOptions ) || new ProxyEmitter( emitter, proxyOptions );

		this.listenTo( proxyEmitter, event, callback, options );
	} else {
		// Execute parent class method with Emitter (or ProxyEmitter) instance.
		EmitterMixin.listenTo.call( this, emitter, event, callback, options );
	}
}

注意到没,这里开始对emitter进行判断,如果是Window对象或者Node对象,那么就采用增加的业务逻辑,否则调用基类的方法进行绑定,因此,我们之间的猜测是正确的。

这里增强的业务逻辑也比较简单,就是根据emitter和配置对象proxyOptions要么创建一个ProxyEmitter,要么采用已经创建好的proxyEmitter进行事件的绑定。

这里我们重点关注这行代码:

this.listenTo( proxyEmitter, event, callback, options );

我们知道这行代码会调用添加事件监听器的事件,因此,我们猜想ProxyEmitter一定会重写添加事件监听器的方法,否则无法绑定dom事件,也就是说具体的装饰增强实际上是在ProxyEmitter 中实现的;所以我们需要看看ProxyEmitter的具体实现:

class ProxyEmitter {
	/**
	 * @param {Node} node DOM Node that fires events.
	 * @param {Object} [options] Additional options.
	 * @param {Boolean} [options.useCapture=false] Indicates that events of this type will be dispatched to the registered
	 * listener before being dispatched to any EventTarget beneath it in the DOM tree.
	 * @param {Boolean} [options.usePassive=false] Indicates that the function specified by listener will never call preventDefault()
	 * and prevents blocking browser's main thread by this event handler.
	 */
	constructor( node, options ) {
		// Set emitter ID to match DOM Node "expando" property.
		_setEmitterId( this, getProxyEmitterId( node, options ) );

		// Remember the DOM Node this ProxyEmitter is bound to.
		this._domNode = node;

		// And given options.
		this._options = options;
	}
}

extend( ProxyEmitter.prototype, EmitterMixin, {
	/**
	 * Collection of native DOM listeners.
	 *
	 * @private
	 * @member {Object} module:utils/dom/emittermixin~ProxyEmitter#_domListeners
	 */

	/**
	 * Registers a callback function to be executed when an event is fired.
	 *
	 * It attaches a native DOM listener to the DOM Node. When fired,
	 * a corresponding Emitter event will also fire with DOM Event object as an argument.
	 *
	 * **Note**: This is automatically called by the
	 * {@link module:utils/emittermixin~EmitterMixin#listenTo `EmitterMixin#listenTo()`}.
	 *
	 * @method module:utils/dom/emittermixin~ProxyEmitter#attach
	 * @param {String} event The name of the event.
	 */
	attach( event ) {
		// If the DOM Listener for given event already exist it is pointless
		// to attach another one.
		if ( this._domListeners && this._domListeners[ event ] ) {
			return;
		}

		const domListener = this._createDomListener( event );

		// Attach the native DOM listener to DOM Node.
		this._domNode.addEventListener( event, domListener, this._options );

		if ( !this._domListeners ) {
			this._domListeners = {};
		}

		// Store the native DOM listener in this ProxyEmitter. It will be helpful
		// when stopping listening to the event.
		this._domListeners[ event ] = domListener;
	},

	/**
	 * Stops executing the callback on the given event.
	 *
	 * **Note**: This is automatically called by the
	 * {@link module:utils/emittermixin~EmitterMixin#stopListening `EmitterMixin#stopListening()`}.
	 *
	 * @method module:utils/dom/emittermixin~ProxyEmitter#detach
	 * @param {String} event The name of the event.
	 */
	detach( event ) {
		let events;

		// Remove native DOM listeners which are orphans. If no callbacks
		// are awaiting given event, detach native DOM listener from DOM Node.
		// See: {@link attach}.

		if ( this._domListeners[ event ] && ( !( events = this._events[ event ] ) || !events.callbacks.length ) ) {
			this._domListeners[ event ].removeListener();
		}
	},

	/**
	 * Adds callback to emitter for given event.
	 *
	 * @protected
	 * @method module:utils/dom/emittermixin~ProxyEmitter#_addEventListener
	 * @param {String} event The name of the event.
	 * @param {Function} callback The function to be called on event.
	 * @param {Object} [options={}] Additional options.
	 * @param {module:utils/priorities~PriorityString|Number} [options.priority='normal'] The priority of this event callback. The higher
	 * the priority value the sooner the callback will be fired. Events having the same priority are called in the
	 * order they were added.
	 */
	_addEventListener( event, callback, options ) {
		this.attach( event );
		EmitterMixin._addEventListener.call( this, event, callback, options );
	},

	/**
	 * Removes callback from emitter for given event.
	 *
	 * @protected
	 * @method module:utils/dom/emittermixin~ProxyEmitter#_removeEventListener
	 * @param {String} event The name of the event.
	 * @param {Function} callback The function to stop being called.
	 */
	_removeEventListener( event, callback ) {
		EmitterMixin._removeEventListener.call( this, event, callback );
		this.detach( event );
	},

	/**
	 * Creates a native DOM listener callback. When the native DOM event
	 * is fired it will fire corresponding event on this ProxyEmitter.
	 * Note: A native DOM Event is passed as an argument.
	 *
	 * @private
	 * @method module:utils/dom/emittermixin~ProxyEmitter#_createDomListener
	 * @param {String} event The name of the event.
	 * @returns {Function} The DOM listener callback.
	 */
	_createDomListener( event ) {
		const domListener = domEvt => {
			this.fire( event, domEvt );
		};

		// Supply the DOM listener callback with a function that will help
		// detach it from the DOM Node, when it is no longer necessary.
		// See: {@link detach}.
		domListener.removeListener = () => {
			this._domNode.removeEventListener( event, domListener, this._options );
			delete this._domListeners[ event ];
		};

		return domListener;
	}
} );

我们首先看构造函数,首先设置一个id,然后将dom节点和配置参数存储起来。关键的方法是看扩展的这个类的原型,我们看到了一个关键的方法就是_addEventListener首先调用attach()

这个关键的attach就是增强的关键方法,为dom节点添加事件:

首先判断事件是否存在,如果存在就不需要添加了。

其次创建一个domListener回调函数,然后在dom节点上绑定对应的事件,并且在回调函数上添加了一个移除监听器的方法,用于销毁创建的监听器。

将新建的回调函数绑定到this._domListeners[event]

有了以上的三个步骤,就完成了给dom节点添加事件的功能,因此增加后的节点可以认为是一个domEmitter。

同时需要注意的是,除了调用attach()方法之外,其实还调用了基类的方法来绑定事件。

以上基本分析了绑定事件的全部过程,至于如果为节点创建id啥的,这部分对主流程影响不大,有兴趣的可以自行分析,移出事件的过程也是类似的道理。这个类的增强将装饰器设计模式的使用发挥到了淋漓尽致的地步,需要仔细体会才能看到代码的精妙之处。

欢迎讨论。

 

 

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