前言
众所周知js是一种单线程,非阻塞的语言,通过事件模型来实现执行模型,那么今天我们就来讨论js的事件循环机制(EventLoop)
同步和异步
关注我的小伙伴肯定知道,有关同步和异步的概念我在promise中就已经聊过了,[详细请看](promise - 掘金 (juejin.cn)),
简单来理解就是:同步按你的代码顺序执行,异步不按照代码顺序执行,异步的执行效率更高。
-同步代码: 不耗时执行的代码
-异步代码: 需要耗时执行的代码
且js引擎会先执行同步代码再执行异步代码。
let a=1
console.log(a);
setTimeout(function(){
a++
},1000)
console.log(a);
所以这段代码两次console.log(a)结果都会是1,setTimeout是异步代码会先被挂起,会先执行第二个console.log(a),所以程序会出现两个1,然后程序才会停止。
进程和线程
百度上对进程和线程的定义是这样的:
进程:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
线程:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
例如:在浏览器打开一个新的页面也叫打开一个新的进程,而一个我们所能看到的页面由多个线程(渲染线程,js线程,http线程)通力合作完成进程任务。
注意:渲染线程和js引擎线程是不能同时工作的,否则会js的加载会阻塞页面的渲染,这也说明了js是单线程的。
单线程有以下优点:
1.节约性能:单线程模型的代码是顺序执行的,所以有助于理解程序的执行流程,便于排查和定位问题,不会产生锁竞争情况
2.节约上下文切换的时间
微任务和宏任务
微任务和宏任务都属于异步代码
-微任务:promise.then(),process.nextTick(),MuationOvserver()
-宏任务:script,setTimeout,setInterval,setImmediate(什么时候完成什么时候执行回调函数,非标准),I/O,UI-rendering页面渲染
事件循环机制
-Eventloop:
1.执行同步代码(宏任务)
2.同步执行完毕后检查是否有异步需要执行
3.执行所有微任务
4.微任务执行完毕后,如果有需要就会渲染页面
5.执行异步宏任务,也是开启下一次事件循环
接下来我们来看个例子:
console.log(1);
new Promise((resolve, reject) => {
console.log(2);
resolve()
})
.then(() => {
console.log(3);
})
.then(() => {
console.log(4);
})
setTimeout(() => {
console.log(5);
})
console.log(6);
相信你已经有了答案,接下来我们好好聊聊是怎么回事
1.执行同步代码(宏任务):(1)console.log(1); (2)new Promise((resolve, reject) => { console.log(2); resolve() })(promise.then()是微任务,promise是同步代码),遇到.then()则入任务队列,遇到setTimeout入宏任务队列,(3)console.log(6);
完成第一步可以得到:1 2 6
2.同步执行完毕后检查是否有异步需要执行:微任务队列和宏任务队列非空,需要执行异步
3.执行所有微任务:先后执行then()中的console.log(3);和console.log(4),得到:3 4
4.微任务执行完毕后,如果有需要就会渲染页面:不需要渲染
5.执行异步宏任务,也是开启下一次事件循环:执行setTimeout开启下一次事件循环,内部只有同步的console.log(5)没有异步代码,得到:5
我们再来看一题:
console.log(1);
new Promise((resolve, reject) => {
console.log(2);
resolve()
})
.then(() => {
console.log(3);
setTimeout(() => {
console.log(4);
}, 0)
})
setTimeout(() => {
console.log(5);
setTimeout(() => {
console.log(6);
}, 0)
}, 0)
console.log(7);
具体过程如下:
1.执行同步代码(宏任务):(1)console.log(1); (2)new Promise((resolve, reject) => { console.log(2); resolve() }),遇到第6行的.then()则入任务队列,遇到第12行的setTimeout入宏任务队列,(3)console.log(7);
完成第一步可以得到:1 2 7
2.同步执行完毕后检查是否有异步需要执行:微任务队列和宏任务队列非空,需要执行异步
3.执行所有微任务:先后执行第6行的then()中的console.log(3),第8行的setTimeout进入宏队伍队列。该过程完成后得到:3
4.微任务执行完毕后,如果有需要就会渲染页面:不需要渲染
5.执行异步宏任务,也是开启下一次事件循环:根据入队列顺序先执行第12行的setTimeout,开启下一次事件循环,先执行同步再执行异步,得到 (5)console.log(5);再执行第8行的setTimeout,执行(6)console.log(4);最后再是第14行的setTimeout入宏任务队列并执行得到(7) console.log(6)。完成该工程得到:5 4 6
所以结果为:1273546
最后我们来一个难一点的
console.log('script start');
async function async1() {
await async2()
console.log('async1 end');
}
async function async2() {
console.log('async2 end');
}
async1()
setTimeout(function() {
console.log('setTimeout');
}, 0)
new Promise(function(resolve, reject) {
console.log('promise');
resolve()
})
.then(() => {
console.log('then1');
})
.then(() => {
console.log('then2');
})
console.log('script end');
这道题不仅考察了事件循环机制还有async的一个机制:await会将后续的代码阻塞进微任务队列中
因此具体过程如下:
1.执行同步代码(宏任务):(1)console.log('script start'); 预编译async1方法和async方法,第10行调用async1方法,因为await存在先执行aysnc2,执行async2中的(2)console.log('async2 end'),并将第5行的console.log('async1 end')阻塞进微任务队列中;执行到第11行的setTimeout放入宏任务队列,再执行第14行的promise(同步代码),执行(3)console.log('promise');第18行和第21行的then()依次进入微任务队列,执行第24行的代码,执行(4)console.log('script end');
完成第一步可以得到:
script start
async2 end
promise
script end
2.同步执行完毕后检查是否有异步需要执行:微任务队列和宏任务队列非空,需要执行异步
3.执行所有微任务:因为第5行的console.log('async1 end')阻塞进微任务队列中,所以第5行代码最先进微任务队列,再是第18行和第21行,所以先后执行(5)console.log('async1 end');(6)console.log('then1'),(7)console.log('then2');
完成这一步可以得到:
async1 end
then1
then2
4.微任务执行完毕后,如果有需要就会渲染页面:不需要渲染
5.执行异步宏任务,也是开启下一次事件循环:执行第11行setTimeout,(8)console.log('setTimeout'); 得到:setTimeout
因此:
script start
async2 end
promise
script end
async1 end
then1
then2
setTimeout
结尾
事件循环机制的过程我们要熟记,只要理解并熟记相信你在面对hr的“刁难”也可以娓娓道来,好好学习,天天向上!