美文网首页
Js中的Event Loop&任务队列

Js中的Event Loop&任务队列

作者: 十年之后_b94a | 来源:发表于2021-12-31 17:55 被阅读0次

前言

Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。
以下内容仅为我个人理解,如有言误请及时通知我。

任务

用个现实的例子我们俩比喻js中的任务,比如一个人一天,要打扫卫生,吃饭,上厕所,工作等。。。但是这些事情不可能同时进行,同时吃饭&上厕所🐶,所以我们就要一个顺序,做完某件事接着做另一件事,所以我们规划出一个任务队列,在js中同理

JavaScript中,任务被分为两种,一种宏任务,一种叫微任务。

宏任务
script全部代码、setTimeoutsetIntervalsetImmediateI/OUI Rendering

微任务
Process.nextTick(Node独有)、PromiseObject.observe(废弃)、MutationObserver

执行顺序

Javascript 有一个主线程和 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。执行顺序当主线程的任务执行完成之后,他会往微任务中拿取任务直到微任务队列中没有任务了,再往宏任务队列中拿取任务,这就是一次事件轮询。在任务队列中执行的顺序就是先进先出的原则。请记住这句话,当遇到该问题之后 就不会做了。

案例

我这里的案例是往上一次的代码中增加代码。

基础案例

console.log('start')
setTimeout(()=>{
  console.log('time')
})
Promise.resolve().then(()=>{
  console.log('promise')
})
console.log("end")

当理解了我们的上述的执行原则,我们就很简单的就能说出答案
start =>end=>promise=>time

image.png

脑海中自然的能想到是这样的图绘,当主线程执行完结束之后,就回去微任务中找任务队列,当微任务队列中执行完之后在执行宏任务队列,此时就完成一次事件轮询了。

案例升级 -> 微任务中继续执行微任务

console.log('start')
setTimeout(()=>{
  console.log('time')
})
Promise.resolve().then(()=>{
  console.log('promise');
  // 不同点
  Promise.resolve().then(()=>{
    console.log('子promise');
  })
})
console.log("end")

想想此时的执行顺序会是什么?记住我们那句话,直到微任务队列中没有了任务再继续执宏任务。所以此时顺序则是:start =>end=>promise=>子promise=>time

image.png

案例升级 -> 在宏任务中执行微任务

console.log('start')
setTimeout(()=>{
  console.log('time')
  // 不同点
  Promise.resolve().then(()=>{
    console.log('promise - time');
  })
})
Promise.resolve().then(()=>{
  console.log('promise');
  Promise.resolve().then(()=>{
    console.log('子promise');
  })
})
console.log("end")

分析,主线程的两个打印执行完成之后,微任务宏任务队列中各有一个任务,然后执行微任务打印promise,打印完之后,发现一个微任务,那么往微任务队列中增加任务,继续执行微任务打印子promise 然后在执行宏任务 打印 time,继续添加任务至微任务队列中,然后继续执行微任务打印promise - time
所以执行顺序:start =>end=>promise=>子promise=>time=>promise - time

image.png

案例升级->在微任务中执行宏任务

console.log('start')
setTimeout(()=>{
  console.log('time')
  Promise.resolve().then(()=>{
    console.log('promise - time');
  })
})
Promise.resolve().then(()=>{
  console.log('promise');
  Promise.resolve().then(()=>{
    console.log('子promise');
  })
  // 不同点
  setTimeout(()=>{
     console.log('子setTimeout');
  })
})
console.log("end")

接下来我们分析该案例:首先毫无疑问主线程的console先执行,此时我们在看微任务队列和宏任务队列中分别各有一个任务,那么先执行宏任务队列的任务,执行到打印promise,发现有一个微任务,那么继续放入队列中,在发现一个宏任务那么继续放入宏任务队列中,现在微任务队列中还有一个任务,则直接打子promise,此时微任务队列中无任务了,那么转而执行宏任务:首先要知道 现在宏任务队列中有两个任务,保持先进先出原则,那就是先打印time然后发现有一个微任务,放入队列,然后继续轮询,发现微任务队列中有任务,则继续打印promise - time,执行完成之后,此时微任务队列中清空,但是此时宏任务还有一个任务等待执行,所以继续执行宏任务打印子settimeout
执行顺序:start=>end=>promie=>子promise=>time=>promise - time=>子settimeout

image.png

案例再升级 -> 微任务并列执行

console.log('start')
setTimeout(() => {
    console.log('time')
    Promise.resolve().then(() => {
        console.log('promise - time');
    })
})
Promise.resolve().then(() => {
    console.log('promise1');
    Promise.resolve().then(() => {
        console.log('子promise1');
    })
    setTimeout(() => {
        console.log('子setTimeout1');
    })
})
// 不同点
Promise.resolve().then(() => {
    console.log('promise2');
    Promise.resolve().then(() => {
        console.log('子promise2');
    })
    setTimeout(() => {
        console.log('子setTimeout2');
    })
})
console.log("end")

这里分析我们就说白话了,直接画图:看看各个任务队列中的顺序


image.png 执行结果 image.png

案例 promise链式调用

console.log('start')


setTimeout(() => {
    console.log('time')
    Promise.resolve().then(() => {
        console.log('promise - time');
    })
})
Promise.resolve().then(() => {
    console.log('promise1');
}).then(() => {
    console.log('promise2');
    Promise.resolve().then(() => {
        console.log('promise2 --- 1');
    })
}).then(() => {
    console.log('promise3');
}).then(() => {
    console.log('promise4');
})

Promise.resolve().then(() => {
    console.log('promise1 - next');
}).then(() => {
    console.log('promise2 - next');
}).then(() => {
    console.log('promise3 - next');
}).then(() => {
    console.log('promise4 - next');
})

console.log("end")

promise的链式调用的执行顺序是上一次的then 执行完毕之后在继续执行新一次的then调用,所以在微任务队列中的任务顺序一定要清晰。

image.png

定时器模块

什么是定时器模块?定义一个定时器任务,那么该任务是什么时机放入宏任务队列中的?要知道定时器是有一个间隔时间设置的,众所周知,时间间隔设置的越低,该任务最先执行,所以说当一个定时器设置的时间间隔到了之后,再把任务推进宏任务队列,然后再按照先进先出的原则,执行任务。

看看这一个案例:如果主线程中定义一个定时器,并设置时间为2秒,但是在主线程任务执行完毕远超2秒,那我们想象,当主线程任务执行完毕,是会等待2秒之后执行定时器中的任务,还是说立即就执行了定时器的任务?看代码

setTimeout(()=>{
  console.log('time')
},2000)
// 假设这个for循环执行时间超过2秒(因电脑配置不同,这段程序执行的时间并不一致)
for(let i = 0 ;i <10000;i++){
  console.log('');
}

仔细观察结果:我们会发现当主线程for循环执行完成之后,并没有等待2秒,而是立马执行了定时器任务;也就是说,当程序运行时,settimeout会被放入定时起模块,并且开始计时,当时间一到就推送至宏任务队列,但是并不影响主线程任务执行,当主线程、微任务执行完毕之后,就会执行宏任务队列了。

setTimeout(()=>{
  console.log('time - 10')
},10)
setTimeout(()=>{
  console.log('time - 9')
},9)
for(let i = 0 ;i <10000;i++){
  console.log('');
}

此时我们绘制运行图:

image.png
此时运行图还没有执行主线程任务,定时器模块中有两个,time-10先进入,但是它的时间间隔大于后面那个定时器任务,所以time-9先进入宏任务队列中。

Promise微任务处理逻辑

关于promise我们知道,在promise的构造函数体中 这一部分代码时同步代码,也就是在主程序运行的代码,而then调用则在构造函数体中返回状态(resolve,reject)之后在执行。

console.log('start');
setTimeout(()=>{
  console.log('time')

  new Promise((resolve)=>{
    console.log('promise - time')
    resolve();
   }).then(()=>{
    console.log('then - time')
  })
})

new Promise((resolve)=>{
  console.log('promise')
  resolve();
}).then(()=>{
  console.log('then')
})
console.log('end')

相信通过上面的一些案例,这里的执行顺序你应该心里很明白。主线程的任务不用说值的注意的是promise的同步代码也是同步执行的。
执行顺序:start->promise->end->then->time->promise - time-> then-time

Dom渲染任务

虽然我们在上诉大篇幅讲了主线程、微任务、宏任务,但是浏览器内核本事是多线程的 image.png

,那我们来探讨下Dom渲染这个任务是发生在哪个环节?

在讲解这个Dom渲染时,可以网上翻翻宏任务中有哪几种类型?我们这一节关注:script全部代码、 UI Rendering,我们知道按照我们的习惯一般script脚本会放在</body>,我们都知道这是因为防止我们操作不了dom等所以放在body体内,但是今天我们看一段代码,你就会知道除了这个原因还有额外的原因,这里今天我们不探讨scritpt属性asyncdefer,因为这两个属性会影响js脚本执行的顺序。

<!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>
    <script>
      //主要是这里代码。不管是因为你引入外部的js脚本还是直接写入代码 执行的效果都是一样的
        for (let i = 0; i < 1000000; i++) {
            console.log(' ');
        }
    </script>
</head>

<body>
    Event Loop 测试
</body>

</html>

我们仔细看页面的渲染,会发现网页渲染会有一段较长时间的空白,之后在加载出文字。这就意味着js脚本会影响浏览器渲染dom的时机。这是为什么呢?因为我们上面的js的任务没干完,所以Dom渲染的任务,就会排在上一个宏任务的后面,所以我们一般把js脚本放在body体内,虽然页面的icon一直在转,但是dom渲染的任务已经执行完毕了。

相关文章

  • Js中的Event Loop&任务队列

    前言 Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种...

  • Event Loop

    JS 主线程不断的循环往复的从任务队列中读取任务,执行任务,这种运行机制称为事件循环(event loop)推荐看...

  • 十九、JS中的同步异步(Event Loop)------ 20

    1、浏览器执行JS代码的机制: 2、等待任务队列(Event Queue): 3、简单例子(未区分宏任务、微任务)...

  • 20211021

    1、js里的事件循环机制(event loop)答:js事件循环中有异步队列有两种:宏任务队列(macro)和微任...

  • js运行机制

    如何理解js的单线程 一个时间之内js只能干一件事 什么是任务队列和Event Loop 同步任务,异步任务异步任...

  • js event loop 与 任务队列 理解

    1.http://obkoro1.com/2018/06/17/Js-%E7%9A%84%E4%BA%8B%E4%...

  • Java异步机制学习小结

    由于Java程序天生就是运行多线程环境下的,所以没有类似js的Event loop(执行栈—任务队列机制),也没有...

  • 宏队列与微队列

    js执行时有两个异步队列:宏队列和微队列。优先执行微队列中的任务,而且每次执行完宏队列中的任务后,都会查看微队列中...

  • Flutter—SomeTips03

    1:Dart 的两个队列分别是 Event queue 事件队列 MicroTask queue 微任务队列 其 ...

  • 关于宏任务微任务的题

    第一道题: 解析:js任务队列有两种,宏任务队列,微任务队列。js的事件循环调度的就是宏任务队列。一个宏任务执行完...

网友评论

      本文标题:Js中的Event Loop&任务队列

      本文链接:https://www.haomeiwen.com/subject/vlxzcltx.html