十分钟弄懂JS事件环、宏任务(附面试题)

59 阅读2分钟

JS事件循环机制

前置知识之浏览器进程

  • 进程:计算机调度的最小单位

  • 线程:进程调度的最小单位

  • 浏览器进程

    • 每一个页面都是一个单独的进程(互不影响)
    • 浏览器也有一个主进程(用户界面)
    • 渲染进程,浏览器内核为每个页面都分配了一个浏览器进程
    • 网络进程(处理请求)
    • GPU进程(3d绘制)
    • 第三方插件进程
  • 渲染进程(包含多个线程)!!!

  • GUI渲染线程(渲染页面)

  • JS引擎线程,与GUI渲染线程执行时机互斥(避免css与js同时操作dom)

  • 事件触发线程(独立的线程) -- eventloop!!!

  • 事件处理线程,click、setTimeout、ajax

  • 事件处理机制之宏任务、微任务

    • 宏任务:由宿主环境(浏览器、node)提供的异步方法,都是宏任务,如script脚本执行、UI渲染
    • 微任务:由语言标准(JS)提供的,如Promise.then、mutationObserver
    • 2022-05-10-20-39-06.png
  • 事件机制简述

    1. 执行首轮宏任务(一般是JS脚本)
      • 执行同步任务
      • 将微任务放入微任务队列[先入后出]
      • 将宏任务放入宏任务队列[先入后出]
    2. 同步任务执行完毕
    3. 清空/执行微任务队列
      • 执行同步任务
      • 将微任务放入微任务队列[先入后出]
      • 将宏任务放入宏任务队列[先入后出]
    4. 等待本轮宏任务/微任务代码执行造成的UI渲染:(JS执行与GUI渲染共用同一个进程)。
    5. 开启下一轮宏任务
  • 图解:2022-05-10-17-03-30.png

code

<!-- - [代码](code/eventloop/demo.html) -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="btn">按钮</div>
    <script>
        document.body.style.background = 'green';
        console.log(1);
        Promise.resolve().then(()=>{
            console.log(2)
            document.body.style.background = 'red';
        })
        // for(let i =0;i<10000000000000;i++){} 等这个执行完,才执行微任务,然后才渲染页面
        console.log(3)
        /*
            根据图解,浏览器执行顺序
                当前宏任务 => 微任务 => GUI渲染引擎(不一定会执行) => 宏任务...
                    所以第一次修改background时,不会理解渲染页面
                    只有当微任务执行完毕时才渲染,此时background已是红色,所以直接是红色的页面
        */

        // document.body.style.background = 'green';
        // console.log(1);
        // setTimeout(() => {
        //     console.log(2)
        //     document.body.style.background = 'red';
        // }, 10);
        // console.log(3)
        //  当前宏任务 => 微任务 => GUI渲染引擎(不一定会执行) => 宏任务...
        //  绿变红,一闪而过


        /*
            事件与宏任务的关系
                如果用户行为,则认为是新的宏任务,点击时将任务放入宏任务队列待执行
                如果是代码直接同步执行的,则认为是当前宏任务直接执行
        */

        function click1(){
            console.log('click1')
            Promise.resolve().then(()=>console.log('promise click1'))
        }
        function click2(){
            console.log('click2')
            Promise.resolve().then(()=>console.log('promise click2'))
        }
        let btn = document.getElementById('btn');
        btn.addEventListener('click',click1)
        btn.addEventListener('click',click2)
        btn.click(); // 代码同步调用 => click1() && click2() => click1 、click2 、 promise click1 、promise click2
        // 用户点击 => 执行两个宏任务,并在执行完宏任务的时候清除其中的微任务 => click1 、promise click1 、click2 、 promise click2


    </script>
    <script src="./face.js"></script>
</body>
</html>

interview

// 小菜

new Promise(function(resolve){
console.log('promise1'); // 1
resolve()
}).then(function(){
// 微任务2
console.log('promise2') // 3
})
// 同步
console.log('script end') // 2


// 正菜
async function async1(){
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
async function async2(){
    console.log('async2')
}
console.log('script start') // 1

setTimeout(() => { // 宏任务
    console.log('setTimeout')
}, 0);

async1()

/**
 * 题解:
 *   关键:  async本质为generator + co => 执行的结果返回值为一个promise
 *     await => yield => 中断代码,并执行返回的promise.then(),直至返回一个普通值
 *   
 *   分析:
 *       默认执行(浏览器情况下,node情况下情况不同,高版本node与浏览器同步了) 
 *     1.script start 
 *       setTimeout开始计时,计时完毕后放入宏任务中。
 *     2 async1 start 
 *        async1() 
 *        await asycn2    
 *          若async2返回值为promise,则执行其then方法。【并将async1后续逻辑放入then中】
 *          若async2返回值不为promise,则将其包装成promise。【并将async1后续逻辑放入then中】
 *          async2中的代码,正常执行。
 *         async1 end 后续:被包装到Promise.then()中执行 -- 微任务队列1:
 *     3 => async2 返回一个promise并将async1后续的代码放入其then方法中执行 => async1 end 放入微任务队列1
 *     4. promise1 => new Promise.then => 放入微任务队列2
 *     5. script end
 *     6. async1 end // 同步任务执行完毕,执行微任务1
 *     7. promise2 // 微任务1执行完毕,执行微任务2
 *     8. setTimeout // 微任务执行完毕,执行宏任务1
 *       微任务队列 [setTimeout]
 *       宏任务队列 [async1 end,promise2]
 *   
 */