1.0.2 • Published 3 years ago
event-api2 v1.0.2
通过addEventListener注册事件
- 统一useCapture和options的情况,确认是冒泡还是捕获阶段
let useCapture = false; //默认在冒泡阶段
if(typeof options === 'boolean') {
useCapture = options;
} else if(options instanceof Object && typeof options.capture === 'boolean') {
useCapture = options.capture;
}
- 使用domEventMap的Map结构来存储具体的dom和其所注册的事件,将事件放进指定冒泡/捕获阶段的优先级的数组中
export const domEventMap = new Map();
- 已有了同样类型同样优先级同样listener的事件只存一次
const type = useCapture ? 'capture' : 'bubble';
if(!eventMap[eventName][type]) {
eventMap[eventName][type] = {};
}
// 将事件放进指定冒泡/捕获阶段的优先级的数组中
if(eventMap[eventName][type][priority]) {
// 有了同样类型同样优先级同样listener的事件只存一次
if(!eventMap[eventName][type][priority].includes(listener)) {
eventMap[eventName][type][priority].push(listener);
}
} else {
eventMap[eventName][type][priority] = [listener];
}
- 依据domEventMap的key遍历,寻找祖孙关系,以便处理冒泡过程父元素中绑定的事件,最终得到的domEventMap结构如下:
var domEventMap = {
[parent]: {
parents: [body],
click: {
capture: {
1: [fn],
},
bubble: {
1: [fn],
2: [fn1, fn2]
},
},
},
[child]: {
parents: [body, parent],
click: {
capture: {
1: [fn],
},
bubble: {
1: [fn],
2: [fn1, fn2]
},
},
},
[body]: {
click: {
capture: {
1: [fn],
},
bubble: {
1: [fn],
2: [fn1, fn2]
},
},
},
}
通过dispatchEvent派发事件
派发阶段会去执行domEventMap中对应dom的事件,分捕获和冒泡两个阶段执行:
- 捕获阶段1:先执行parents中的capture数组
if(eventMap.parents) {
for(let i = 0; i < eventMap.parents.length; i++) {
const parentEventMap = domEventMap.get(eventMap.parents[i]) || {};
execEvents(parentEventMap, eventName, 'capture');
}
}
- 捕获阶段2:再执行dom本身的capture数组
execEvents(eventMap, eventName, 'capture');
- 冒泡阶段1: 先执行dom本身的bubble数组
execEvents(eventMap, eventName, 'bubble');
- 冒泡阶段2: 再倒序执行parents中的bubble数组
if(eventMap.parents) {
for(let i = eventMap.parents.length - 1; i >= 0; i--) {
const parentEventMap = domEventMap.get(eventMap.parents[i]) || {};
execEvents(parentEventMap, eventName, 'bubble');
}
}
通过removeEventListener移除事件
- 从domEventMap中根据dom获取到对应的eventMap进行遍历,只有同样的dom,类型,listener,阶段的事件才会移除
尽可能保证一帧的时间(16ms)中所有事件的执行时间之和不超过 10ms(暂时无需考虑超过 10ms 的单个事件),需要把在这一帧来不及执行的事件放到下一帧执行(依旧需要按照优先级来执行)
改造dispatchEvent,把原有的执行的地方改成推进任务队列
const taskList: Listener[] = []; //存放任务队列 const eventMap = domEventMap.get(dom) || {}; // 捕获阶段,先将parents中的capture的事件推进任务队列 if(eventMap.parents) { for(let i = 0; i < eventMap.parents.length; i++) { const parentEventMap = domEventMap.get(eventMap.parents[i]) || {}; // execEvents(parentEventMap, eventName, 'capture'); pushEventToTaskList(parentEventMap, eventName, 'capture', taskList); } } // 捕获阶段,再将dom本身的capture的事件推进任务队列 // execEvents(eventMap, eventName, 'capture'); pushEventToTaskList(eventMap, eventName, 'capture', taskList); // 冒泡阶段,先将dom本身的bubble的事件推进任务队列 // execEvents(eventMap, eventName, 'bubble'); pushEventToTaskList(eventMap, eventName, 'bubble', taskList); // 冒泡阶段,再将parents中的bubble的事件推进任务队列 if(eventMap.parents) { for(let i = eventMap.parents.length - 1; i >= 0; i--) { const parentEventMap = domEventMap.get(eventMap.parents[i]) || {}; // execEvents(parentEventMap, eventName, 'bubble'); pushEventToTaskList(parentEventMap, eventName, 'bubble', taskList); } } // 执行任务队列 execTaskList(taskList);
最后使用requestAnimationFrame来执行队列在每一帧执行队列
function execTaskList(taskList: Listener[]) { requestAnimationFrame(handler); function handler(timestamp: number) { let taskFinishTime: number = window.performance.now(); while (taskFinishTime - timestamp < 10) { const nextTask = taskList.shift(); if (nextTask) { console.log(window.performance.now(), nextTask); nextTask(); } taskFinishTime = window.performance.now(); } //任务队列不为空,就继续在下一帧执行 if (taskList.length > 0) { requestAnimationFrame(handler); } } }