Event Loop

132 阅读3分钟

先上一道常见的笔试题

console.log('1');
async function async1() {
    console.log('2');
    await async2();
    console.log('3');
}
async function async2() {
    console.log('4');
}
 
process.nextTick(function() {
    console.log('5');
})
 
setTimeout(function() {
    console.log('6');
    process.nextTick(function() {
        console.log('7');
    })
    new Promise(function(resolve) {
        console.log('8');
        resolve();
    }).then(function() {
        console.log('9')
    })
})
 
async1();
 
new Promise(function(resolve) {
    console.log('10');
    resolve();
}).then(function() {
    console.log('11');
});
console.log('12')

上面简化图解可拆分为三部分:

一、JavaScript引擎

*Memory Heap 内存堆 —— 这是内存发生分配的地方

*Call Stack 调用栈 —— 这是代码运行时栈帧存放的位置

二、Runtime 运行时

我们要知道的是,像setTimeOut DOM AJAX,都不是由JavaScript引擎提供,而是由浏览器提供,统称为Web APIs

三、EventLoop

1、关于javascript javascript是一门单线程语言,虽然HTML5提出了Web-works这样的多线程解决方案,但是并没有改变JaveScript是单线程的本质。

2、javascript事件循环 既然js是单线程的,就是同一时间只能做一件事情。那么问题来了,我们访问一个页面,这个页面的初始化代码运行时间很长,比如有很多图片、视频、外部资源等等,难道我们也要一直在那等着吗?答案当然是 不能

所以就出现了两类任务:

同步任务

异步任务

同步异步任务分别进入不同的 '‘场所'’ 执行。所有同步任务都在主线程上执行,形成一个执行栈;而异步任务进入Event Table并注册回调函数 当这个异步任务有了运行结果,Event Table会将这个回调函数移入Event Queue,进入等待状态 当主线程内同步任务执行完成,会去Event Queue读取对应的函数,并结束它的等待状态,进入主线程执行 主线程不断重复上面3个步骤,也就是常说的Event Loop(事件循环)。 那么我们怎么知道什么时候主线程是空的呢?

js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。

3、setTimeoutsetInterval setTimeout(fn,0)这里的延迟0秒时什么意思呢?

含义是,当主线程执行栈内为空时,不用等待,就马上执行。

setIntervalsetTimeout类似,只是前者是循环的执行。对于执行顺序来说,setInterval会每隔指定的时间将注册的函数置入Event Queue,如果前面的任务耗时太久,那么同样需要等待。

对于setInterval(fn,ms)来说,我们已经知道不是每过ms秒会执行一次fn,而是每过ms秒,会有fn进入Event Queue。一旦setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了。

4、promiseprocess.nextTickasync/await 事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。

除了广义的同步任务和异步任务,我们对任务有更精细的定义:

micro-task(微任务):Promise,process.nextTick,且process.nextTick优先级大于promise.then。可以理解是在当前 task 执行结束后立即执行的任务;
setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.then()在本轮“事件循环”结束时执行。

不同类型的任务会进入对应的Event Queue:

Promise中的异步体现在then和catch中,所以写在Promise中的代码是被当做同步任务立即执行的。

await实际上是一个让出线程的标志。await后面的表达式会先执行一遍,将await后面的代码加入到microtask中,然后就会跳出整个async函数来执行后面的代码;

因为async await 本身就是promise+generator的语法糖。所以await后面的代码是microtask