一篇文章快速搞懂JavaScript事件循环(微任务宏任务),同步异步和阻塞非阻塞
Posted 啊a阿花
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一篇文章快速搞懂JavaScript事件循环(微任务宏任务),同步异步和阻塞非阻塞相关的知识,希望对你有一定的参考价值。
🔄事件循环🔄
事件循环可以理解为我们编写的javascript代码和浏览器或者node之间的桥梁,桥梁之间他们通过回调函数进行沟通。无论是我们的文件IO,数据库操作,定时器,子进程,在完成对应的操作之后都会将结果和回调函数放到事件循环(任务队列)中去。事件循环会不断的从任务对列中取出对应的事件(回调函数)放入函数的调用栈中执行。
比如说JavaScript执行一个文件读写的操作,但是JavaScript不具备文件读写功能,就会通过v8引擎翻译后去调用node.js bindings的库,然后进入到libuv中,libuv再去连接操作系统对文件进行读写。返回结果时worker threads将系统调用的结果放入到事件队列中,再通过事件循环传入到JavaScript端,让回调函数执行
🔞JavaScript代码执行顺序🈲
事件循环其实维护着两个队列,一个微任务(promise的then和catch,queueMicrotask()等)队列,一个宏任务(dom监听,ajax请求,setInterval,setTimeout等)队列,具体执行顺序如下
- 函数的调用栈,JavaScript是单线程从上往下执行,先进栈的后出栈,栈中函数执行完毕全部弹出才会执行微队列和宏队列
console.log('log')
function sum() {
console.log('sum')
}
function foo() {
sum()
console.log('foo')
}
foo();
//log
//sum
//foo
- 栈的规则是先进后出,按理说最先压入栈的是console.log(‘log’)(console也是函数),会在栈底,最后才会被调用执行,但是为什么却先执行呢?
- 因为consloe.log(‘log’)只有一个打印操作,一执行就结束了,执行结束就会弹出栈。
- 而在执行foo函数(压栈)的时候,还没执行完又调用了sum函数,就会又将sum函数压入栈中,按照后进先出的规则sum函数后进栈则先出栈,最后foo函数执行完毕弹出
- 微任务队列,调用栈中函数执行完毕之后,微队列中的任务入栈
- 当微队列为空之后,宏队列中的任务入栈
const message = '优先执行1'
console.log(message)
setTimeout(() => {
console.log("宏队列最后执行")
})
new Promise(function(resolve) {
resolve();
console.log('优先执行2')
}).then(function() {
console.log("微队列后执行");
});
// 优先执行1
// 优先执行2
// 微队列后执行
// 宏队列最后执行
🔴IO阻塞和🟢非阻塞,👣同步和🦿异步的区别
1️⃣阻塞和非阻塞是相对于被调方用来说的(也就是操作系统),操作系统提供了两种调用方式。调用时可以随意选取阻塞式调用还是非阻塞式调用
- 阻塞式调用
操作文件时,调用结果返回之前当前线程处于阻塞态(阻塞态时cpu是不会分配时间片的),线程只会在得到调用结果之后才会回到就绪态得到时间片继续执行 - 非阻塞式调用
操作文件时,调用执行后当前线程不会停止执行,只需要过一段时间来检查一下有没有返回结果(因为此时前面的未执行完,没有完全拿到返回结果)
虽然看着非阻塞式IO相对阻塞IO不会产生堵塞的情况,但是也有一些问题——需要频繁的去确定是否已经获取到了全部的数据。不断的确定的过程称为轮询,只有完全获取到数据后才能传给程序做相关的处理。
在node.js中的libuv中提供了一个线程池,会负责相关的操作,通过轮训或者其他方式等待结果。当线程池调用某个线程完成轮训操作后,会通过事件循环将本次得到的数据和之前注册的回调函数放到某个事件队列里面,然后进入函数的调用栈执行
2️⃣同步和异步是相对于调用方来说的。
- 如果发起调用后不会进行其他任何操作,只是等待结果,这个过程就是同步调用。
- 如果发起调用之后并不会等待结果,继续完成后续的工作,等到有回调了之后再去执行,这个过程就是异步调用
以上是关于一篇文章快速搞懂JavaScript事件循环(微任务宏任务),同步异步和阻塞非阻塞的主要内容,如果未能解决你的问题,请参考以下文章