npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

event-api2

v1.0.2

Published

1. 统一useCapture和options的情况,确认是冒泡还是捕获阶段 ```javascript let useCapture = false; //默认在冒泡阶段 if(typeof options === 'boolean') { useCapture = options; } else if(options instanceof Object && typeof options.capture === 'boolean') { useCapture = optio

Downloads

5

Readme

通过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);
  1. 最后使用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);
    }
  }
}