事件循环

161 阅读3分钟

背景

进程

  • 如果把浏览器比喻成一个公司, 那么一个部门(标签页)就是一个进程
  • 大多数app都只有一个进程, 浏览器比较特殊

线程

  • 如果把浏览器比喻成一个公司, 那么一个部门就是一个进程, 每个部门里的员工就是一个线程
  • 如果说一根筷子🥢就是一个线程, 那么一把筷子就是一个进程

浏览器的进程有:

  • 渲染进程, 子进程, 处理 html css js
  • 网络进程, 子进程, 负责网络请求
  • 浏览器主进程, 董事会
  • 每一个tab对应一个进程, 每打开一个tab就创建一个进程, 未来将不会采用这种模式
  • 未来趋势: 每一个域名对应一个进程, 打开bilibili创建一个进程, 打开youtobe创建一个进程

渲染主线程

  • 渲染主线程是一个非常繁忙的员工, 他既要处理html, 又要处理css, 还要处理js 
  • 单线程, 工作量巨大...
  • 为了更好的完成工作, 他采用了一种叫事件循环的工作方式

JS为什么是单线程

  • 因为他执行在浏览器的渲染主线程中
  • 而浏览器的渲染主线程只有一个

事件循环

微任务队列

  • chrom浏览器中有两个队列微任务队列 其他很多队列
  • 微任务队列是所有队列中优先级最高的队列
  • 这里我们将其他很多队列统称为宏任务队列

宏任务队列

  • 延时队列: setTimeout
  • 交互队列: onClick
  • ...还有很多记不起名的队列
  • 以上队列统称为宏任务队列
  • 为了方便理解,下面我们只考虑两个队列: 宏任务队列微任务队列

伪代码模拟

  • 渲染主线程在执行同步任务时会产生一些宏任务微任务
  • 其他如事件监听线程, 也会产生一些宏任务
  • 这些异步任务会被存放到宏任务队列微任务队列
  • 渲染主线程执行完同步任务
  • 优先执行微任务,
  • 清空微任务队列后再执行宏任务
const microTaskQueue = []
const macroTaskQueue = []

// 染主线程在执行同步任务时会产生一些宏任务, 微任务
// 这些异步任务会被存放到宏任务队列, 微任务队列
// try {} catchMicor {} 是伪代码, 表示捕获微任务
// try {} catchMacor {} 是伪代码, 表示捕获宏任务
function run(fn: Function) {
  try {
    fn()
  } catchMicro (microFn) {
    microTaskQueue.push(microFn)// 执行过程中产生了微任务, 就放到微任务队列
  } catchMicro (macroFn) {
    macroTaskQueue.push(macroFn)// 执行过程中产生了宏任务, 就放到宏任务队列
  }
}
// loop 的回调函数将不停的执行
loop(() => {
  if (microTaskQueue.length) {
    const fn = microTaskQueue.shift()
    fn()
  }
  else {
    const fn = macroTaskQueue.shift()
    fn && fn()
  }
})

观察渲染阻塞

优先级

  • 微任务队列 大于 render队列,
  • render队列 大于 延时队列,
  • 延时队列最低
const p = document.createElement("p").innerHTML = "new paragraph";
document.body.appendChild(p);

setTimeout(() => {
  const list = document.getElementsByTagName("p");
  console.log("timeout----", list.length);
  alert("宏任务阻塞");
});

Promise.resolve().then(() => {
  const list = document.getElementsByTagName("p");
  console.log("promise.then----", list.length);
  alert("微任务阻塞");
});