Ckeditor源码之事件系统

54 阅读2分钟
CKEDITOR.event.implementOn = function (targetObject) {
  var eventProto = CKEDITOR.event.prototype;

  for (var prop in eventProto) {
    if (targetObject[prop] == null)
      targetObject[prop] = eventProto[prop];
  }
};

CKEDITOR.event.prototype = (function () {
  var eventEntry = function (eventName) {
    this.name = eventName;
    this.listeners = [];
  };

  eventEntry.prototype = {
    // Get the listener index for a specified function.
    // Returns -1 if not found.
    getListenerIndex: function (listenerFunction) {
      for (var i = 0, listeners = this.listeners; i < listeners.length; i++) {
        if (listeners[i].fn == listenerFunction)
          return i;
      }
      return -1;
    }
  };

  function getEntry( name ) {
    // Get the event entry (create it if needed).
    var events = getPrivate( this );
    return events[ name ] || ( events[ name ] = new eventEntry( name ) );
  }
  return {
    define: function (name, meta) {...},
    on: function (eventName, listenerFunction, scopeObj, listenerData, priority) {...},
    once: function (eventName, listenerFunction, scopeObj, listenerData, priority) {...},
    capture: function (eventName, listenerFunction, scopeObj, listenerData, priority) {...},
    fire: function (eventName, data, editor) {...},
    fireOnce: function (eventName, data, editor) {...},
    removeListener: function (eventName, listenerFunction) {...},
    removeAllListeners: function () {...},
    hasListeners: function (eventName) {...},
  }
}())

implementOn函数的功能是把CKEDITOR.event.prototype的属性和方法挂载到指定对象上:

CKEDITOR.event.implementOn( CKEDITOR );
CKEDITOR.event.implementOn( CKEDITOR.editor.prototype );

CKEDITOR.event.prototype定义了事件的基本功能:

  • define: 定义事件
  • on: 添加事件监听
  • once: 添加一次性事件监听
  • capture: 添加捕获阶段的事件监听
  • fire: 触发事件
  • fireOnce: 触发一次性事件
  • removeListener: 移除事件监听
  • removeAllListeners: 移除所有事件监听
  • hasListeners: 判断某个事件是否有监听

通过 new eventEntry生成事件实例,通过getEntry方法挂载到指定对象上上,例如在ckeditor的实例中事件是注册在ckeditor._.events上。

fire的具体实现:

fire: ( function() {
 // Create the function that marks the event as stopped.
 var stopped = 0;
 var stopEvent = function() {
     stopped = 1;
   };

 // Create the function that marks the event as canceled.
 var canceled = 0;
 var cancelEvent = function() {
     canceled = 1;
   };

 return function( eventName, data, editor ) {
   // Get the event entry.
   var event = getPrivate( this )[ eventName ];

   // Save the previous stopped and cancelled states. We may
   // be nesting fire() calls.
   var previousStopped = stopped,
     previousCancelled = canceled;

   // Reset the stopped and canceled flags.
   stopped = canceled = 0;

   if ( event ) {
     var listeners = event.listeners;

     if ( listeners.length ) {
       // As some listeners may remove themselves from the
       // event, the original array length is dinamic. So,
       // let's make a copy of all listeners, so we are
       // sure we'll call all of them.
       listeners = listeners.slice( 0 );

       var retData;
       // Loop through all listeners.
       for ( var i = 0; i < listeners.length; i++ ) {
         // Call the listener, passing the event data.
         if ( event.errorProof ) {
           try {
             retData = listeners[ i ].call( this, editor, data, stopEvent, cancelEvent );
           } catch ( er ) {}
         } else {
           retData = listeners[ i ].call( this, editor, data, stopEvent, cancelEvent );
         }

         if ( retData === EVENT_CANCELED )
           canceled = 1;
         else if ( typeof retData != 'undefined' )
           data = retData;

         // No further calls is stopped or canceled.
         if ( stopped || canceled )
           break;
       }
     }
   }

   var ret = canceled ? false : ( typeof data == 'undefined' ? true : data );

   // Restore the previous stopped and canceled states.
   stopped = previousStopped;
   canceled = previousCancelled;

   return ret;
 };
} )

相比eventEmitter的emit,fire多了stopEvent和cancelEvent, 用来手动停止事件监听函数的执行。 fire函数通过闭包存储了stopped和canceled状态,在遍历执行监听函数的时候,把stopEvent和cancelEvent抛给事件监听函数,在监听函数中如果调用了stopEvent或cancelEvent,就会停止该事件后续监听事件的执行。

具体使用:

 function first() {
   alert("first", Array.from(arguments))
 }

 function second() {
   console.log("second", Array.from(arguments))
 }

 const myEvent  = new CKEDITOR.event();

 // 注册事件
 myEvent.on("custom", first, null, { extra: "data-first" }, 1);
 myEvent.on("custom", second, null, { extra: "data-second" }, 2);

 // 触发事件
 myEvent.fire("custom", null, { data: "test-data" });

 // 移除事件监听
 myEvent.removeAllListeners();