阅读 386

浅谈浏览器事件循环——Event loop

一、引言

众所周知,JS是一门单线程异步非阻塞的编程语言。那单线程是如何做到异步的呢?

二、单线程和异步

在JS中,只有一个线程处理JS任务,这也意味着所有任务只能同步执行。如果遇到网络请求,那就必须等待请求结果返回,JS才能继续往下执行。这明显是不合情理的。所以浏览器引入了Event Loop机制来帮助处理这种长时间挂起的任务。

三、浏览器事件循环模型

本篇文章不涉及浏览器底层真实运行环境,只对浏览器的事件循环模型进行抽象,感性理解:

概念理解

  1. 调用栈(Call stack): JS引擎唯一工作线程,用于函数调用执行
  2. WebApis: 浏览器提供的事件,如:DOM操作,AJAX请求,定时器等等
  3. 任务队列(task queue): 事件循环将完成的webApi事件按顺序排队形成队列,在调用栈为空时,队首的回调函数推入调用栈执行
  4. 微任务队列(microtask queue): 每次当一个任务退出且调用栈空的时候,微任务队列中的每一个微任务会依次被执行。不同的是它会等到微任务队列为空才会停止执行——即使中途有微任务加入。换句话说,微任务可以添加新的微任务到队列中,并在下一个任务开始执行之前且当前事件循环结束之前执行完所有的微任务。

事件循环流程 (未包含渲染)

  1. 执行完所有调用栈上的任务,调用栈为空;
  2. 执行microtask queue中所有 microtask,即使是中途加入的microtask;如microtask queue为空,执行3;
  3. 执行Task Queue 中队首的task;如果调用栈为空,执行2;

image.png

举例说明

<script>
      function task1() {
        console.log('task1')
        task2()
        return
      }
      function task2() {
        console.log('task2')
        task3()
        return
      }
      function task3() {
        console.log('task3')
        return
      }
      document.body.addEventListener('click', () => {
        setTimeout(function handleClick(){
          console.log('handleClick called')
        })
      })
      new Promise((resolve) => {
        document.body.addEventListener('click', () => {
          resolve('promise')
        })
      }).then(function success(value) {
        console.log(value)
      })

      task1()
      document.body.click()
</script>
复制代码
  1. 主函数main推入调用栈,代码从上往下执行,函数声明task1,task2,task3
  2. 添加bodyclick事件处理函数handleClick到WebAPIs,等待事件触发。
  3. 添加promise,当bodyclick事件触发时,调用resolve('promise');
  4. task1调用,task1推入调用栈执行,控制台输出'task1';
  5. task1内部调用task2,task2推入调用栈执行,控制台输出'task2';
  6. task2内部调用task3,task3推入调用栈执行,控制台输出'task3';
  7. task3执行完毕,返回,弹出调用栈;
  8. task2执行完毕,返回,弹出调用栈;
  9. task1执行完毕,返回,弹出调用栈;
  10. document.body.click()click事件触发,监听函数执行;
  11. handleClick推入task queue等待执行; resolve调用,success推入maricotask queue等待执行;
  12. main执行完毕,返回,弹出调用栈;调用栈清空;
  13. 事件循环机制检查调用栈为空,执行microtask queue中的回调函数success,控制台打印'promise';
  14. 检查microtask queue为空,执行task queue中的回调函数handleClick,控制台打印'handleClick called';
文章分类
前端