首先我们要知道JS是单线程的,而对于游览器进程来说,不仅仅有处理JS的线程,还有其他的线程,像渲染页面的GUI线程,维护事件队列的事件触发线程,处理setTimeout,setInterval的定时器触发线程,处理异步HTTP请求的异步HTTP触发线程
例如setTimeout定时任务就是在定时器触发线程中进行的,当定时任务完成后会通知事件触发线程,把相应的回调函数放到事件队列中去,AJAX和Fetch等同理
了解完每个线程对应的任务之后,接下来我们再来正式介绍下Event Loop(事件循环机制)
首先我们知道JS分为同步任务和异步任务,另外setTimeout和setInterval是同步任务,关于其中的回调函数才是异步任务
JS线程有个执行栈,首先全部的同步的代码会在执行栈中执行,当遇到了setTimeout这样的API的时候,就会交给定时器触发线程执行,然后时间到了就会添加到事件队列中去,当执行栈的同步代码执行完成之后,就会检查事件队列中是否有剩余的回调函数,如果有就拿到执行栈中执行,JS就是无限重复这个步骤,所以被称为事件循环
下面我们看一个例子
console.log(1111)
setTimeout(() => {
console.log(3333)
setTimeout(() => {
console.log(5555)
})
})
setTimeout(() => {
console.log(4444)
})
console.log(2222)
// 结果: 1111 -> 2222 -> 3333 -> 4444 -> 5555
从头开始,首先打印1111,然后遇到setTimeout,交给定时器触发线程执行,完成后将相应的回调函数添加到事件队列中去,下面又遇到setTimeout时一样处理,这时事件队列中已经有两个回调函数了,再然后打印2222,这时候执行栈为空了,然后去查看事件队列,首先会把第一个回调函数添加到执行栈中去,打印3333,然后第一个回调函数中又有setTimeout又重复刚才的步骤,当第一个回调函数执行完之后,执行栈为空,又去查看事件队列中的函数,接下来就是重复刚才的步骤,然后打印4444,5555
然后关于Promise的出现,又把JS划分成微任务和宏任务,我们可以将每次执行栈执行的代码当做是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)
下面我们再看一个例子
new Promise((resolve, reject) => {
console.log(1111)
resolve(2222)
}).then(res => {
console.log(res)
})
setTimeout(() => {
console.log(3333)
})
// 结果: 1111 -> 2222 -> 3333
首先执行new Primise打印2222,遇到then之后把then中的回调函数加入微任务队列,再执行setTimeout,按照之前的步骤,放到事件队列中去,当执行栈为空时,首先会执行微任务队列中的函数,执行完成之后又会执行宏任务(如果没有就从事件队列中拿),执行完之后再执行微任务,依次循环
不过关于then中的回调函数的时机应该是resolve执行之后(个人猜测)
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2222)
})
}).then(res => {
console.log(res)
})
new Promise((resolve, reject) => {
resolve(1111)
}).then(res => {
console.log(res)
})
setTimeout(() => {
console.log(3333)
})
// 结果: 1111 -> 2222 -> 3333