js的事件循环,了解一下?

前言

JavaScript从诞生起就是一门单线程的脚本语言,单线程就意味着JavaScript代码在执行的时候,都只会有一个主线程去执行所有的任务。单线程的一个弊端在于,当线程中存在一个耗时非常长的任务时,会容易出现代码阻塞,从而导致页面出现“假死”状态。为了解决这一问题,事件循环机制(Event Loop)就诞生了。

一、同步任务和异步任务

JavaScript中所有的任务被分为同步任务和异步任务。

  • 同步任务:立即执行的任务

  • 异步任务:不会立即执行的任务

  • 常见的异步任务有setTimeout、Promise.then()、异步的ajax请求等。

二、执行栈和事件队列

1、 执行栈

栈是一种数据结构,遵循先进后出的规则。js中的执行栈就具有这样的结构。当代码第一次执行时,js引擎会解析这段代码并将其中的同步任务压到执行栈中,任务完成后被弹出执行栈,继续执行下一个任务。
举个例子:

function A(){
	B()
}
function B(){
	C()
}
function C(){}
A()

存在A、B、C 3个方法,A调用B,B调用C。

  • 代码执行过程中,会先将A压入栈底,由于A中调用了B,所以此时A不会被弹出栈
  • 接着将B压入栈,此时A在栈底,B在栈顶,由于B中还调用了C,所以B还是没有执行完成,不会被弹出栈
  • 接着将C压入栈,此时,栈中的顺序是 栈底A -> B -> C栈顶
  • C中没有调用其他的方法,故C执行完后,执行栈会遵循先进后出的原则依次弹出C、B、A

2、 事件队列(Task Queue)

队列也是一种数据结构,遵循先进先出的规则。上面我们讲的都是关于同步任务是如何执行的,那异步任务执行后会如何呢?

js在遇到异步任务时,并不会一直等待其返回结果,而是将该事件挂起,继续执行主线程中的任务。当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的队列中去,这个队列就称之为事件队列(Task Queue)。

被放在事件队列中的异步任务不会立即的执行其回调,而是等待当前主线程中所有的同步任务都完成,执行栈为空的时候,主线程才会去事件队列读取是否有可执行的异步任务。如果有,会取出该任务并压入执行栈中执行其中的同步代码…如此反复,就形成了无限的循环,直到所有任务都执行完成。这个循环的过程我们就称之为“事件循环(Event Loop)”。
同样,我们举个例子:

console.log('start')
Promise.resolve().then(() => {
    setTimeout(() => {
        console.log('timeout')
    }, 0)
    console.log('promise')
})
console.log('end')
  • 任务开始执行,将同步任务console.log(‘start’) 压入执行栈,执行结束后弹出栈
  • a是异步任务,将Promise callback加入到事件队列中
  • 将同步任务console.log(‘end’)压入执行栈,执行结束后弹出栈
  • 此时主线程上的同步任务执行完成,执行栈为闲置状态,主线程读取事件队列,取出Promise任务并压入执行栈
  • setTimeout为异步任务,加入到事件队列中,继续执行console.log(‘promise’),执行完成,弹出栈
  • 主线程执行完成,继续读取事件队列,取出setTimeout并压入执行栈执行
  • 任务全部执行完成,结束。最终打印结果为start -> end -> promise -> timeout

三、宏任务和微任务

以上描述的事件循环是宏观上的表述,事实上,js的异步任务之间并不相同,它们执行顺序的优先级也不一样。据此,异步任务中又被划分为宏任务(macro task)和微任务(micro task)。微任务的优先级高于宏任务。

宏任务包括:
script(整体代码)、setTimeout、setInterval、setImmediate等。

微任务包括:
Promise、Mutation Observer、async/await等。

前面我们讲到,异步任务会被放到事件队列中。然而,根据任务类型的不同,异步任务又会被分到对应的宏任务队列或微任务队列中。在当前的执行栈为空时,主线程会优先查找微任务队列是否有事件存在,如果有,则取出事件并压入执行栈执行,如果没有,执行宏任务队列的任务…如此反复…进入循环。
在这里插入图片描述

至此,js的事件循环就讲完了,最后,我们来看一段比较复杂的代码,进一步体会一下事件循环机制。

async function a() {
    console.log('a')
    await b()
    console.log('async-after-b')
}
function b() {
    console.log('b')
}
console.log('script-start')
a()
const promise = new Promise(resolve => {
    console.log('promise')
    resolve()
}).then(() => {
    console.log('promise-cb1')
    setTimeout(() => {
        console.log('timeout1')
    }, 0)
}).then(() => {
    console.log('promise-cb2')
})
setTimeout(async () => {
    console.log('timeout2')
    await b()
    console.log('timeout-after-b')
}, 0)
console.log('script-end')

打印结果我就不贴出来了,具体的执行过程我就不做详细的讲解,有兴趣的同学可以细细品一下!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值