1.Ckeditor4事件系统设计
ckeditor4的源码中,事件系统的模块在引入相关模块时候被注入到CKEDITOR.event。
具体的代码见 core/event.js。
后续也直接把其中所有方法暴露到了CKEDITOR上
CKEDITOR.event.implementOn( CKEDITOR );
具体一共向外暴露了 on once capture fire fireOnce removeListener removeAllListeners hasListeners 这些方法,正体设计思路为 事件系统的设计 - 构造函数方式 - 发布订阅 , 其中耦合了一些些和项目业务相关的代码,就不展开了。基于其源码,我也复现了demo,如下
2.自己设计事件系统
class EventManager {
constructor() {
this.events = new Map();
}
on(
eventName,
listenerFunction,
scopeObj = this,
listenerData = {},
priority = 10
) {
// 获取事件,如果事件不存在则创建一个空数组
// todo: 每个事件可以再封装一层,包含事件名,事件监听器,事件状态等
let event = this.events.get(eventName);
if (!event) {
event = {
listeners: [],
};
this.events.set(eventName, event);
}
// 定义监听器触发函数,接受编辑器对象、发布的数据、停止事件、取消事件等参数
const listenerFirer = (editor, publisherData, stopFn, cancelFn) => {
const ev = {
name: eventName,
sender: this,
editor: editor,
data: publisherData,
listenerData: listenerData,
// 调用时候才注册的stop和cancel方法, 在 on 内函数可以对其引用
stop: stopFn,
cancel: cancelFn,
removeListener: () => {
this.removeListener(eventName, listenerFunction);
},
};
const ret = listenerFunction.call(scopeObj, ev);
return ret === false ? EVENT_CANCELED : ev.data;
};
// 设置监听器的属性 如果该监听器没有被绑定,就添加该监听器
listenerFirer.fn = listenerFunction;
listenerFirer.priority = priority;
const listenerIndex = event.listeners.findIndex(
(listener) => listener.fn === listenerFunction
);
if (listenerIndex === -1) {
event.listeners.push(listenerFirer);
event.listeners.sort((a, b) => {
return a.priority - b.priority;
});
}
// 返回一个对象,该对象包含一个 removeListener 方法
return {
removeListener: () => {
this.removeListener(eventName, listenerFunction);
},
};
}
removeListener(eventName, listenerFunction) {
const event = this.events.get(eventName);
if (!event) {
return;
}
const listenerIndex = event.listeners.findIndex(
(listener) => listener.fn === listenerFunction
);
if (listenerIndex !== -1) {
event.listeners.splice(listenerIndex, 1);
if (event.listeners.length === 0) {
this.events.delete(eventName);
}
}
}
removeAllListeners() {
this.events.clear();
}
// 触发事件
fireEvent(eventName, editor, publisherData) {
const event = this.events.get(eventName);
if (!event) {
return;
}
const stopFn = () => {
this.stopEvent(eventName);
};
const cancelFn = () => {
this.cancelEvent(eventName);
};
// 遍历所有监听器,并调用每一个监听器
event.listeners.forEach((listener) => {
// todo: 改成for of 循环,顺序执行通过状态控制是否继续
listener(editor, publisherData, stopFn, cancelFn);
});
}
stopEvent(eventName) {
const event = this.events.get(eventName);
if (!event) {
return;
}
event.listeners.length = 0;
}
cancelEvent(eventName) {
const event = this.events.get(eventName);
if (!event) {
return;
}
event.listeners.length = 0;
}
}
const eventEmit = new EventManager();
// test priority
eventEmit.on(
"test",
(e) => {
console.log(1, e);
},
eventEmit,
{},
100
);
eventEmit.on("test", (e) => {
console.log(2, e);
// 后续监听器不再执行
// e.stop();
});
eventEmit.fireEvent("test", "editor", "publisherData");