JS执行机制你了解多少?一文详解事件循环、微任务、宏任务

253 阅读4分钟

前置知识

JS 是单线程,意味着执行前面代码,后面任务处于等待状态,不同于java多线程可以一次性执行多个事件。这如果遇到执行任务时间长的任务,这样会造成页面渲染不连贯,甚至加载阻塞。

这时候JS引入异步和同步任务两种,所谓“异步”,就是一个任务不连续完成,先完成一部分,回头“回调”执行剩下第二部分,甚至还有第三部分;同步则是主线程的任务,即按顺序执行机制连贯完成。

像文件读取,网络请求等任务属于异步任务:花费时间长,但是中间操作不用JS引擎完成,只需要执行来自别人准备好的数据就行,也就是回调过程。 这种JS“忙等”,常常采用“异步任务回调”模式: 这种方式也叫“非阻塞式”:等待异步任务的同时,JS引擎执行其他同步任务,等到执行栈里面没有任务,再不断看看任务队列里面是否有任务,执行回调。这种模式很显然,完成相同任务的同时,节约时间。

上面已经谈到事件循环(Event loop),这里定义一下。

事件循环,是有队列组成,异步回调遵从先进先出,在JS引擎空闲时候不断去事件队列里面找是否有完成了的异步任务,这也被称为循环。而事件任务分为,宏任务和微任务。下面根据图片看看

image.png 这时候调用栈为空,v8引擎从任务队列回调异步任务到调用栈。当然,浏览器环境下还在处理promise对象下的xhr和点击事件任务。(下文进行讲解,这里只需要知道事件循环这个过程)

事件循环(Event Loop)

事件循环是JavaScript执行机制的核心,它确保宏任务和微任务按顺序执行。事件循环的基本步骤如下:

  1. 执行栈:JavaScript代码开始执行,首先执行同步代码。
  2. 任务队列:异步任务(如setTimeout)会被放入任务队列。
  3. 微任务队列:微任务会被放入微任务队列。
  4. 事件循环:当执行栈为空时,事件循环会检查任务队列和微任务队列,依次执行宏任务和微任务

宏任务和微任务

宏任务(Macro-tasks)

宏任务是JavaScript执行栈中的主要任务,包括代码执行、setTimeoutsetIntervalI/O操作等。宏任务执行完毕后,会检查是否有微任务需要执行,然后进入下一个宏任务。

微任务(Micro-tasks)

微任务是异步任务的一种,它们会在当前宏任务执行完毕后立即执行。微任务包括Promisethen回调、MutationObserver等。

  • Es6 引入promise 对象。
  • js引擎发起异步任务,分为宏任务,微任务
  • 一开始script里面就是宏任务,先执行微任务,再是宏任务

image.png

易错点

  • 全局script 就是宏任务,如果有多个script,被浏览器解析成宏任务
<script>
        console.log(0);
        setTimeout(() => {
            console.log(9);
        },0);
    </script>
    <script>
        console.log(3);  
    </script>
    // 0 3 9
  • await右边的表达式还是会立即执行,表达式之后的代码才是微任务, await微任务可以转换成等价的promise微任务分析
<script>
         async function fn1() {
             await fn2(); // await后面代码才是微任务
             console.log(1);
         }
         async function fn2() {
             console.log(2);
         }
         fn1();
        console.log(3);  
        // 2 3 1
    </script>
  • promise 本身是同步的,后面调用的 .then() 才是微任务
   <script>
       const p = new Promise(resolve => {
             console.log(9);
             resolve(8);
       })
       p.then(res => console.log(res) ); 
       console.log(1);
       // 9 1 8
   </script>

下面使用代码进行讲解:

console.log(1)
setTimeout(() => { 
  console.log(2) 
}, 0) 
const p = new Promise((resolve, reject) => { 
  console.log(3) 
  resolve(4)
}) 
p.then(result =>  console.log(result))
console.log(5)

思考3s

讲解:1. 宏任务队列中的代码首先执行,打印1

  1. setTimeout被添加到宏任务队列中,但不会立即执行。
  2. Promise的构造函数立即执行,打印3
  3. Promisethen方法被添加到微任务队列中。
  4. console.log(5)在宏任务队列中执行,打印5
  5. 微任务队列中的Promisethen方法执行,打印4
  6. 宏任务队列中的setTimeout执行,打印2

打印:1 3 5 4

邀你来挑战

试试成果吧,下面代码执行结果是什么?

<script>
        console.log(2);
        setTimeout(() => {
            console.log(1);
            const p = new Promise(resolve => {
                console.log(9);
                resolve(8);
             })
             p.then(res => console.log(res) )
        }, 0);
        const p = new Promise((resolve,reject) => {
            setTimeout(() => {
                console.log(3);
            }, 0);
            resolve(4);//打印成功 4
        })
       
        const p2 = new Promise( resolve => resolve(5) )
        p2.then(res => console.log(res)) 

        p.then (res => console.log(res))

        console.log(7);
        
    </script>

请在评论区打出你的结果吧