JS的执行机制
先看段代码
console.log(1) // 同步
setTimeout(function() {// 异步
console.log(2)
}, 10)
new Promise((resolve) => {
console.log('promise') // 同步
resolve()
}).then(() => {
console.log('then') // 异步
})
console.log(3) // 同步
// 执行栈中,同步先执行,输出 1
// 异步任务的回调函数加入到任务队列。(console.log(2)和console.log('then'))
// 接着同步任务,输出 promise 和 3
// 同步任务执行完成,如果异步任务有了结果(10ms过后)
// 将任务队列中异步的回调函数加入到执行栈中执行,输出 2 then(队列先进先出)
// 理想结果:1 promise 3 2 then
// 正确输出:1 promise 3 then 2
结果显然不对,两个异步任务console.log(2)和console.log('then')执行顺序错了,队列不是先进先出的吗?2先进去,then后进去,为什么结果却反过来了?它们都是异步任务,这时单单拿同步和异步来解释已经解释不同了。
所以,就引出了微任务和宏任务===>===>===>===>===>===>===>===>===>===>===>===>
微任务:Promise,process.nextTick
宏任务:整体代码script,setTimeout,setInterval
执行机制变成:先执行整体代码(宏任务),执行完成后,执行微任务,微任务执行完,第一轮事件循环完毕;开启第二轮:读取任务队列,看是否有宏任务,如果有就执行……
上面代码,因为promise是微任务,所以它优先于setTimeout执行
下面来详细了解一下什么是宏任务,什么是微任务吧===>===>===>===>===>=====>===>===>===>
宏任务:
所谓的宏任务,就是需要其它线程处理的任务,这里的其它线程包括JS引擎线程,事件触发线程,定时器线程,异步网络请求线程,所以一旦和上面4个线程挂钩,它就是宏任务,我们拿上面列出的宏任务———解释下:
整体代码script(JS引擎线程)
setTimeout,setInterval(定时器线程)
ajax异步请求(异步网络请求线程)
onclick事件绑定(事件触发线程)
是不是比上面给出的宏任务种类多?所以,记住:凡是涉及到浏览器内核线程的任务,都是宏任务。
微任务:
微任务通常来说就是需要在当前任务执行结束后立即执行的任务,比如异步的任务但又不需要除JS引擎线程处理的任务(除字很关键!)
首先它是异步的,其次,它不需要诸如事件触发进程或定时器线程或异步网络请求线程来处理。来看看Promise,process.nextTick,浏览器中有专门的的内核线程来处理吗?你可能会说它是由JS引擎线程执行的啊,确实,但它只是个异步,所以是微任务。
你可以这么理解:不是宏任务的任务就是微任务。
事件循环(Event-Loop):
事件循环就是就是JavaScript的执行机制,即执行流程:
- 执行整段
JavaScript
代码,将整段代码中的同步任务放入执行栈中执行(上面说了,这个是宏任务) - 代码中如果有
setTimeout
或ajax
等宏任务,会利用对应的浏览器的内核线程来处理,达到条件(定时器时间达到,请求完成)后,由事件触发线程
将其对应的回调加入到事件队列(任务队列)中 - 如果有
Promise
等微任务,加入微任务队列,在执行栈执行完当前的同步任务的之后,从微任务队列中取出微任务,立即执行 - 所有的微任务执行完,此时,执行栈中处于闲置状态(注意:本轮事件循环中所有微任务执行完,开启下轮循环)
- 以上是第一轮事件循环,以下开始第二轮:
- 事件队列将队列中的任务加入到执行栈,按先进先出的顺序执行
- 如果此时进入栈中的任务既有同步任务,微任务和宏任务,那先执行同步任务
- 再执行所有的微任务
- 第二轮事件循环结束,开始第三轮循环:
- 执行宏任务...循环往复,直到所有任务执行完毕。
下面看段代码:
console.log(111) setTimeout(function() { console.log(222) }, 0) new Promise((resolve) => { console.log('333') resolve() }).then(() => { console.log('444') }) $.ajax({ url: '', success: function() { console.log(555) } }) console.log(666)
- 运行代码,先执行整段代码的同步任务
console.log(111)
,输出 111 - 遇到
setTimeout
,将其交给定时器线程
处理,0秒后,才由事件触发线程
将其回调函数交给事件队列 - 遇到
Promise
,Promise为立即执行函数,所以输出 333 - 它有一个
then
回调,是个微任务,先不执行 - 遇到
ajax
请求,将其交给异步网络请求线程
处理,请求完成响应200后,由事件触发线程
将其成功的回调函数交给事件队列 - 此时假设ajax请求完成,故现在的事件队列为 ①
setTimeout回调
,②ajax回调
- 执行同步代码
console.log(666)
,输出 666 - 同步任务执行完,查看是否有微任务,有微任务,为
then
回调,开始执行,输出 444 - 没有其他微任务了,此时执行栈为空,第一轮事件循环结束,准备开始第二轮事件循环
- 去事件队列中取任务
- 首先取出
setTimeout
的回调,输出 222 - 然后取出
ajax
的回调,输出 555 - 结果:111 333 666 444 222 555