JavaScript异步机制——Event-Loop


函数调用栈和消息队列

  • 函数调用栈

我们知道,js是单线程的,也就是说只有一个线程在执行js的代码。当引擎第一次遇到 JS 代码时,会产生一个全局执行上下文并压入调用栈。函数执行完毕后,该函数就会在调用栈中被清除,然后函数调用栈继续:进入下一个函数 => 执行该函数 => 弹出该函数 这样一个往复的流程。

  • 消息队列

消息队列又叫 任务队列,是一个有序的异步任务集合列表,当代码里有异步操作的时候,它们不需要立刻被执行,当代码执行到他们的时候,并不会立刻把它们压入函数调用栈 ,而是先放到消息队列中等待,等到消息队列中的任务执行完毕后,才按照一定的规则(Event-Loop)将消息队列里的函数推入到函数调用栈。消息队列分为两种:宏任务(macro-task)队列和 微任务(micro-task) 队列。任务的读取方式为先进先出,后进后出(存在例外,如 process.nextTick等)。

常见的宏任务包括:1、DOM操作 2、用户交互(事件) 3、网络任务(ajax)4、定时器:setTimeOut、setImmediate

微任务指的是ES6之后和 Promise 相关的所有语法:Promise、async/await等等, process.nextTick 在微任务中优先级最高

Event-Loop

JS是单线程的语言,所有的异步都要通过异步实现,而Event-Loop 就是JavaScript的异步实现机制。

一道Event-Loop题目

        setTimeout(() => { //9
            console.log('immediate')
        })

        async function async1() {
            console.log('async1 start') // 同步 2
            await async2() // 同步
            console.log('async1 end') // 7 await 后面的内容,异步(微任务),相当于 callback 函数里的内容
        }

        async function async2() {
            console.log('async2') // 3
        }

        console.log('script start') //1

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

        async1() // 同步

        new Promise(function (resolve) {
            console.log('promise1') // 同步 4
            resolve()
        }).then(function () {
            console.log('promise2') // 8
        })


        console.log('script end') // 同步完成 5

上述的打印入注释所示,结果如下图所示,那么为什么是按照这个顺序打印呢,这涉及到Event-Loop的实现机制。

在这里插入图片描述

图解 Event-Loop 渲染机制

  • 先把微任务队列里的任务拿出来交给全局执行上下文执行
  • 再把宏任务队列里的任务拿出来交给全局执行上下文执行
  • 不断的循环

以上为 Event-Loop 的基本执行规则,以文章开头题目为例进行讲解

        setTimeout(() => { // 9
            console.log('immediate')
        })

        async function async1() {
            console.log('async1 start') // 同步 2
            await async2() // 同步
            console.log('async1 end') // 7 await 后面的内容,异步(微任务),相当于 callback 函数里的内容
        }

        async function async2() {
            console.log('async2') // 3
        }

        console.log('script start') //1

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

        async1() // 同步

        new Promise(function (resolve) {
            console.log('promise1') // 同步 4
            resolve()
        }).then(function () {
            console.log('promise2') // 8
        })


        console.log('script end') // 同步完成 5

1 代码开始运行,此时函数调用栈获取全局上下文。 JS线程首先执行到定时器: setTimeout(() => { console.log(‘immediate’) }), 它是个宏任务,被推到宏任务队列:

在这里插入图片描述

2 随后代码执行至:console.log(‘script start’),它不属于异步任务,会立刻被推入函数调用栈,立刻执行

在这里插入图片描述

3 代码继续执行:setTimeout(function () { console.log(‘setTimeout’) //10 }, 0),它也是个宏任务,被推到宏任务队列

在这里插入图片描述

4 随后代码执行至:async1(),进入async1后,它会相继执行以下代码:

	console.log('async1 start') 
    await async2() 
    console.log('async1 end')

在前一篇文章中提到过,async 之后的代码并不是微任务, await相当于 promise 中的 then 方法,它会把后续的任务会变为微任务执行,因此,console.log(‘async1 start’)async2() 作为同步任务会立刻被打印, console.log(‘async1 end’) 作为异步微任务则被推入微任务队列。

await fn() 类似于 const a = fn() ,fn的执行先于a的声明

在这里插入图片描述

5 随后代码执行至 new Promise 部分,console.log(‘promise1’) 作为同步任务,立刻进入函数调用栈被执行,then方法中的任务作为异步被推送至微任务队列

在这里插入图片描述

6 最后,执行至console.log(‘script end’) 作为同步任务,立刻进入函数调用栈被执行。 至此,同步任务已经全部执行完毕,异步任务分别被推入对应的消息队列

在这里插入图片描述

7 同步任务全部执行完毕后函数调用栈被清空,微任务队列中的任务依次进栈被调用。

在这里插入图片描述

8 宏任务全部执行完毕后函数调用栈被清空,微任务队列中的任务依次进栈被调用。

在这里插入图片描述

至此,event-loop执行完毕

DOM渲染时机

Javascript 和DOM渲染在浏览器内核中共用一个线程,因为 Javascript 可修改DOM结构。浏览器的实际执行应该是:

  • 先把函数调用栈里的同步任务拿出来交给全局执行上下文执行
  • 再把微任务队列里的微任务拿出来交给全局执行上下文执行
  • 执行DOM渲染
  • 把宏任务队列里的宏任务拿出来交给全局执行上下文执行
  • 重复上述过程

总结

函数调用栈和消息队列

Event-Loop

  • 一道Event-Loop题目
  • 图解 Event-Loop 渲染机制

DOM渲染时机

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值