解剖事件环
- js是单线程,是指主线程。但主线程之内还有很多非单线程的内容,比如定时器,比如promise,比如http请求。
- 在浏览器事件环机制中,当执行栈的同步代码清空后,系统会去读取任务队列。什么叫执行栈的同步代码清空?其实就是主线程之内能一步过的代码运行完毕。那么什么叫任务队列?其实就是那些非单线程的内容,js执行的时候,会把这些异步的内容先保存起来,统一放到一个队列里,等待主线程里的同步代码运行完毕后再来处理这些任务,所以叫任务队列。
- 所谓任务队列,其实也是有分别的。这里面分两大类:宏任务和微任务。常见的宏任务包括setTimeout,setImmediate(只有ie支持),setInterval,messageChannel。常见的微任务则包括Promise.then(),mutationObserver。
- 既然任务队列有分别,那么处理这些任务的时候就有了优先级————优先处理微任务。把微任务清空后(如果处理过程中有新的微任务,在处理当前微任务后会马上处理新的微任务,并在下次执行宏任务前全部清空),再依次读取宏任务,这里特别注意,并非一次性执行完所有宏任务,而是像队列那样,先取一个宏任务执行,执行完后,再去看是否有微任务,如果有,则执行微任务,然后再读取一个宏任务执行,不断循环。
- node.js中的事件环跟浏览器里的事件环机制又有区别:在nodejs的事件环机制中,是优先执行微任务,但是当执行完微任务,进入宏任务的时候,即使在执行宏任务过程中存在新的微任务,也不会优先执行微任务,而是把宏任务队列中执行完毕。
- async函数立即执行。
- awaite函数里的表达式立即执行,await后面的代码则相当于向微任务队列里添加了微任务,await的作用是让出一个线程。
- 例子:
setTimeout(() => {
console.log('我是setTimeout1')
Promise.resolve().then(()=>{
console.log('我是Promise1')
})
}, 0);
Promise.resolve().then(()=>{
console.log('我是Promise2')
setTimeout(() => {
console.log('我是setTimeout2')
}, 0);
Promise.resolve().then(()=>{
console.log('我是Promise3')
})
console.log('我是新插进来的')
})
console.log("我是最后一个")
打印顺序为:
'我是最后一个' -> '我是Promise2' -> '我是新插进来的' -> '我是Promise3' -> '我是setTimeout1' -> '我是Promise1' -> '我是setTimeout2'
解析:
js
执行,碰到setTimeout
,丢到任务队列里(宏任务),碰到promise,丢到任务队列里(微任务);先执行完主线程里的单线程,也就是执行栈里的同步代码,输出“我是最后一个”。
- 开始处理任务队列。
- 优先处理微任务,打印'我是Promise2',碰到
setTimeout
,继续丢到任务队列,向下执行,输出'我是Promise3'。
- 微任务处理完毕,去看看任务队列里有没有宏任务。
- 处理先进去的那个
setTimeout
,输出'我是setTimeout1',向下又发现了一个微任务Promise,丢到任务队列。
- 好的,一个宏任务已经完成了,去看看有没有微任务,发现了刚丢进去的
promise
,处理一下,输出'我是Promise1'。
- 微任务全处理了,去看看有没有微任务,发现了最后丢进去的那个碰到
setTimeout
,进行处理,输出'我是setTimeout2'。至此全部输出完毕。