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("微队列后执行");
});
console.log('优先执行3')
// 优先执行1
// 优先执行2
// 优先执行3
// 微队列后执行
// 宏队列最后执行
🔄事件循环🔄
事件循环可以理解为我们编写的JavaScript代码和浏览器或者node之间的桥梁,桥梁之间他们通过回调函数进行沟通。无论是我们的文件IO,数据库操作,定时器,子进程,在完成对应的操作之后都会将结果和回调函数放到事件循环(任务队列)中去。事件循环会不断的从任务队列中取出对应的事件(回调函数)放入函数的调用栈中执行。
简单点说就是同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入任务队列。主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。 上述过程的不断重复就是 Event Loop (事件循环)。
🔴IO阻塞和非阻塞🟢
1️⃣阻塞和非阻塞是相对于被调方用来说的(也就是操作系统),操作系统提供了两种调用方式。调用时可以随意选取阻塞式调用还是非阻塞式调用
- 阻塞式调用
操作文件时,调用结果返回之前当前线程处于阻塞态(阻塞态时cpu是不会分配时间片的),线程只会在得到调用结果之后才会回到就绪态得到时间片继续执行 - 非阻塞式调用
操作文件时,调用执行后当前线程不会停止执行,只需要过一段时间来检查一下有没有返回结果(因为此时前面的未执行完,没有完全拿到返回结果)
虽然看着非阻塞式IO相对阻塞IO不会产生堵塞的情况,但是也有一些问题——需要频繁的去确定是否已经获取到了全部的数据。不断的确定的过程称为轮询,只有完全获取到数据后才能传给程序做相关的处理。
在node.js中的libuv中提供了一个线程池,会负责相关的操作,通过轮训或者其他方式等待结果。当线程池调用某个线程完成轮训操作后,会通过事件循环将本次得到的数据和之前注册的回调函数放到某个事件队列里面,然后进入函数的调用栈执行
👣同步和异步的区别🦿
2️⃣同步和异步是相对于调用方来说的。
- 如果发起调用后不会进行其他任何操作,只是等待结果,这个过程就是同步调用。
- 如果发起调用之后并不会等待结果,继续完成后续的工作,等到有回调了之后再去执行,这个过程就是异步调用