关于JavaScript事件环

80 阅读3分钟
本文围绕以下几点展开讨论

什么是事件环(Event Loop)?
事件环是如何运行的?

首先,我们要了解,浏览器是多进程的,每一个Tab网页都是一个进程,每一个进程里都存在多个功能线程,接下来只取两个线程进行讨论(JS引擎线程和GUI渲染线程)。

JS引擎线程:负责解析和执行JavaScript脚本,一个tab只有一个JS引擎线程,也就是单线程。
GUI渲染线程:负责浏览器页面HTML的渲染和绘制,页面发生重绘或回流,该线程会执行
两个线程之间的关系是互斥的,其中一个线程在执行时,另一个线程必定是挂起的。

一丶 那什么是事件环(Event Loop)?

我们先对JS的运行进行几个点的划分:

JS执行栈:对应的是< script >全局执行上下文。
异步任务执行队列:先进先出的存储模式(队列),分为两种任务,如下
宏任务:setTimeout / setInterval / setImmediate
微任务:promise.then / mutationObserver / process.nextTick
GUI渲染:渲染页面。

1643303539(1).png

简单说:
1 - JS执行全局上下文时,会将异步任务分别放入宏任务队列和微任务任务队列。
2 - 待同步任务执行完毕以后,先清空微任务队列内的任务。
3 - GUI渲染线程执行,渲染页面。
4 - 宏任务队列取出第一个任务的回调(callBack),放入JS执行栈。
5 - 回到第一步,继续按以上顺序执行。
总结 - 这就是JavaScript事件环。

二丶 结合demo讲解事件环的运行?

    var p = new Promise(resolve => {
      console.log(1);
      resolve(2);
    })

    function fn1() {
      console.log(3);
    }

    function fn2() {
      // 宏任务1 setTimeout1
      setTimeout(() => {
        console.log(4);
      });
      fn1();
      console.log(5);
      //微任务1 p.then1
      p.then(resolve => {
          console.log(resolve);
        })
        //微任务2 p.then2
        .then(() => {
          console.log(6);
        })
    }
    fn2();

    /** 
     *   第一步
     *    全局执行上下文 执行阶段
     *        1 - new Promise 代码块是同步执行的    执行console.log(1);       
     *        2 - 执行fn2();
     *        3 - 存在延时器 setTimeout   直接给宏任务队列 添加 任务1 setTimeout1的回调
     *        4 - 执行fn1();    执行console.log(3);   
     *        5 - console.log(5); 
     *        6 - 存在 p.then   直接给 微任务队列 添加任务  微任务1 p.then1的回调  微任务2 p.then2的回调
     *        执行完毕: 输出 1  ->  3  ->5
     *  
     * 
     *    宏任务队列
     *    任务1:setTimeout1的回调
     * 
     *    微任务队列
     *    p.then1 的回调
     *    p.then2 的回调
     */



     /** 
     *   第二步
     *      清空微任务队列,
     *          1: 将 微任务的 回调函数 放入  JS执行栈执行  
     *                console.log(resolve);
     *                console.log(6);
     *          2: 执行结果  分别是    2 -> 6
     *          3: 微任务队列清空
     *  
     * 
     *    宏任务队列
     *    任务1:setTimeout1的回调
     * 
     *    微任务队列
     */


     /** 
     *   第三步
     *      执行GUI线程,渲染页面
     * 
     *    宏任务队列
     *    任务1:setTimeout1的回调
     * 
     *    微任务队列:空
     */


     /** 
     *   第四步
     *      1 - 从宏任务队列中,取出第一个宏任务的回调,放入 JS执行栈 
     *              console.log(4);
     *    宏任务队列:空
     * 
     *    微任务队列:空
     */


    /** 
     *   第五步
     *      1 - JS执行栈 执行代码 
     *              console.log(4);
     * 
     *      2 - 执行结果  输出 4
     *    宏任务队列:空
     * 
     *    微任务队列:空
     */


     /** 
     *   之后的 延续 第二步 执行  形成一个 闭环循环。
     *   最后结果  输出  1 3 5 2 6 4 
     */