JavaScript事件循环机制 (Event Loop)你真的懂了吗?

43 阅读3分钟

一、js单线程、非阻塞

JavaScript的主要用途是与用户互动,以及操作DOM。如果它是多线程的会有很多复杂的问题要处理,比如有两个线程同时操作DOM,一个线程删除了当前的DOM节点,一个线程是要操作当前的DOM阶段,最后以哪个线程的操作为准?为了避免这种,所以JS是单线程的。即使H5提出了web worker标准,它有很多限制,受主线程控制,是主线程的子线程。

非阻塞:通过 event loop 实现。

二、代码执行机制

2.1 同步任务和异步任务

  • 同步任务 即可以立即执行的任务,例如 console.log() 打印一条日志、声明一个变量或者执行一次加法操作等。
  • 异步任务 相反不会立即执行的事件任务。异步任务包括宏任务微任务(后面会进行解释~)。
  • 常见的异步操作:
    • Ajax
    • DOM的事件操作
    • setTimeout
    • Promise的then方法
    • Node的读取文件
x2.jpg

任务队列: 异步代码的执行,遇到异步任务不会等待它返回结果,而是将这个任务挂起,继续执行执行栈中的其他任务。当异步任务返回结果,将它放到任务队列中,被放入任务队列不会立刻执行起回调,而是等待当前执行栈中所有任务都执行完毕,主线程空闲状态,主线程会去查找任务队列中是否有任务,如果有,则取出排在第一位的任务,并把这个任务对应的回调放到执行栈中,然后执行其中的同步代码。

2.2 宏任务(Macrotasks)和微任务(Microtasks)

宏任务:

  • script(整体代码)
  • setTimeout()
  • setInterval()
  • postMessage
  • I/O
  • UI交互事件

微任务:

  • new Promise().then(回调)
  • MutationObserver(html5 新特性)

Event Loop

异步任务中的宏任务和微任务,还没有弄明白。我们可以先顺一遍执行机制:

  • 从全局任务 script开始,任务依次进入栈中,被主线程执行,执行完后出栈。
  • 遇到异步任务,交给异步处理模块处理,对应的异步处理线程处理异步任务需要的操作,例如定时器的计数和异步请求监听状态的变更。
  • 当异步任务达到可执行状态时,事件触发线程将回调函数加入任务队列,等待栈为空时,依次进入栈中执行。

到这问题就来了,当异步任务进入栈执行时,是宏任务还是微任务呢?

  • 由于执行代码入口都是全局任务 script,而全局任务属于宏任务,所以当栈为空,同步任务任务执行完毕时,会先执行微任务队列里的任务。
  • 微任务队列里的任务全部执行完毕后,会读取宏任务队列中拍最前的任务。
  • 执行宏任务的过程中,遇到微任务,依次加入微任务队列。
  • 栈空后,再次读取微任务队列里的任务,依次类推。

实例解析

    console.log('start')

    setTimeout(function() {
      console.log('setTimeout')
    }, 0)

    Promise.resolve().then(function() {
      console.log('promise1')
    }).then(function() {
      console.log('promise2')
    })

    console.log('end')
3297464-b92bc5547761aefa.webp

心得: 同一阶段,同步代码执行完毕,当执行栈为空,处理异步任务先微后宏。

三、经典题目分析

          Promise.resolve()
            .then(function () {
                console.log("promise0");
            })
            .then(function () {
                console.log("promise5");
            });
        setTimeout(() => {
            console.log("timer1");
            Promise.resolve().then(function () {
                console.log("promise2");
            });
            Promise.resolve().then(function () {
                console.log("promise4");
            });
        }, 0);
        setTimeout(() => {
            console.log("timer2");
            Promise.resolve().then(function () {
                console.log("promise3");
            });
        }, 0);
        Promise.resolve().then(function () {
            console.log("promise1");
        });
        console.log("start");
        // 打印结果: start promise0 promise1 promise5 timer1 promise2 promise4 timer2 promise3

script第一个宏任务,同级微任务一次全部执行完注意入队顺序,宏任务一个一个执行。

    console.log("script start");
    async function async1() {
      await async2(); // await 隐式返回promise
      console.log("async1 end"); // 这里的执行时机:在执行微任务时执行
    }
    async function async2() {
      console.log("async2 end"); // 这里是同步代码
    }
    async1();
    setTimeout(function() {
      console.log("setTimeout");
    }, 0);
    new Promise(resolve => {
      console.log("Promise"); // 这里是同步代码
      resolve();
    })
      .then(function() {
        console.log("promise1");
      })
      .then(function() {
        console.log("promise2");
      }); 
    console.log("script end");

    // 打印结果:  script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout

async隐式返回Promise,会产生一个微任务,await后面的代码是在微任务时执行