前言
我们知道Javascript是一门单线程语言,所谓单线程语言,就是指一次只能完成一件任务;我们可以想象成单线程是一个一心一意,用情专一的痴情少年。Js在执行代码时,默认会从上到下依次执行,执行过程中会将任务分成两类:同步任务和异步任务。
同步任务:又叫做非耗时任务,指的是在主线程上排队执行的那些任务,前面的任务执行完毕才能执行后面的任务。
异步任务:又叫做耗时任务,没有立马执行但是需要被执行的任务,会放在任务队列里面。
什么是浏览器事件循环过程?
JavaScript代码的执行过程中,除了依靠调用栈来搞定函数的执行顺序外,还依靠任务队列来搞定另外一些代码的执行。整个执行过程,我们称为事件循环过程,浏览器事件循环过程就是浏览器V8引擎去执行代码的过程。
调用栈:v8内部维护出来一个用来存放函数的执行上下文环境的一个栈结构
为了更好弄清浏览器事件循环机制,我们需要了解一下进程与线程。
进程和线程
- 都是Cpu工作时间片的描述。
- 进程描述的是Cpu在运行指定以及加载和保存上下文所需要的时间。
- 线程用来描述进程中执行一段指令所需要的时间。
- 进程包含线程,一个进程有一个或者多个线程。
- 浏览器新开一个tap页,相当于是新建了一个进程,该进程中有渲染线程(GPU绘制),js引擎线程,http请求线程,等等;其中渲染线程 和 js 引擎线程 互斥。
宏任务与微任务
Js执行代码过程中会将代码分成两类:同步代码和异步代码。Js会先执行同步代码,而异步代码会去到任务队列中。
而任务队列又分为宏任务队列(macrotask)(task)和微任务队列(microtask)(jobs),那哪些是宏任务?哪些是微任务呢?
宏任务:
UI-rendering渲染事件(解析Dom,计算布局,绘制)- 用户的交互事件
- js脚本执行这件事(同步代码)
- 网络请求,文件读写
setTimeout,setInterval,setImmdiate,I/O
微任务:
promise.then()MutationObserverprocess.nextTick()
浏览器事件循环顺序(Event-Loop)
- 执行同步代码(这属于宏任务)
- 当调用栈为空后,查询是否有异步代码需要执行
- 执行所有的微任务
- 有必要的话会渲染页面
- 开始下一轮的Event Loop,也就是执行宏任务
我们可以理解为宏任务开始就是一次事件循环的开始,重复循环上述步骤形成一个事件循环,各任务的执行先后关系:同步任务 > 微任务 > UI渲染 > 宏任务
任务队列执行“先进先出”的顺序
面试考题
请说出下面代码的输出顺序:
//执行同步代码
console.log('start');
async function async1() {
await async2()//await 会阻塞它下一行代码的执行
console.log('async1 end');//会挤入微任务队列 【async1 end】
}
async function async2() {
console.log('async2 end');
}
async1()
//会放到宏任务队列
setTimeout(() => {
console.log('setTimeout');
}, 0)
//同步代码
new Promise(resolve => {
console.log('Promise');
resolve()
})
.then(() => {//到微任务队列中,没执行的话后面的.then跟没接一样 此时微任务队列【Promise1 async1 end】
console.log('Promise1');
})
.then(() => {//执行完了上一个.then微任务,发现又产生了一个微任务,紧接着就执行输出'Promise2'
console.log('Promise2');
})
console.log('end');
//输出顺序
//start
//async2 end
//Promise
//end
//async1 end
//Promise1
//Promise2
//setTimeout
具体分析已在代码中标注。
结语
做前端开发事件循环机制是必须需要掌握的内容,它是前端极其重要的基础知识。除了本文讲到的浏览器事件循环机制,NodeJs也有事件循环机制,它们的实现是有很大差别的。了解事件循环机制对我们理解Js的执行过程,今后的学习有很大的帮助。