1.0.2 • Published 3 years ago

event-api2 v1.0.2

Weekly downloads
-
License
ISC
Repository
-
Last release
3 years ago

通过addEventListener注册事件

  1. 统一useCapture和options的情况,确认是冒泡还是捕获阶段
  let useCapture = false; //默认在冒泡阶段
  if(typeof options === 'boolean') {
    useCapture = options;
  } else if(options instanceof Object && typeof options.capture === 'boolean') {
    useCapture = options.capture;
  }
  1. 使用domEventMap的Map结构来存储具体的dom和其所注册的事件,将事件放进指定冒泡/捕获阶段的优先级的数组中
export const domEventMap = new Map();
  1. 已有了同样类型同样优先级同样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];
  }
  1. 依据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. 捕获阶段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');
    }
  }
  1. 捕获阶段2:再执行dom本身的capture数组
  execEvents(eventMap, eventName, 'capture');
  1. 冒泡阶段1: 先执行dom本身的bubble数组
  execEvents(eventMap, eventName, 'bubble');
  1. 冒泡阶段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移除事件

  1. 从domEventMap中根据dom获取到对应的eventMap进行遍历,只有同样的dom,类型,listener,阶段的事件才会移除

尽可能保证一帧的时间(16ms)中所有事件的执行时间之和不超过 10ms(暂时无需考虑超过 10ms 的单个事件),需要把在这一帧来不及执行的事件放到下一帧执行(依旧需要按照优先级来执行)

  1. 改造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);
  2. 最后使用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);
        }
      }
    }
1.0.2

3 years ago

1.0.1

3 years ago

1.0.0

3 years ago