js运行机制
event loop事件循环
- js分为同步任务和异步任务,所有的同步任务都在主线程上执行
- 另外存在着一个“任务队列”,只要异步的任务有了结果,便在任务队列里面加入一个事件
- 当主线程的的同步任务都执行完了,执行栈清空,这个时候会去读取任务队列,依次把他们扔到主线程执行
- 这个过程不断循环,就成了js的事件循环机制。
所以我们不难理解有的时候setTimeout(fn, 0)没有立即执行,它只是被立即加入到任务队列了,可能那个时候主线程还没有执行完毕,所以它要等着,等js引擎空闲的时候再执行。
任务队列是由js的事件触发线程控制,不是js引擎所控制,可能js引擎太忙了,浏览器又单独开了一个线程
宏任务(macrotask) 、微任务(microtask)
如果我们遇到诸如类似的,那么谁先执行呢,这就引出了宏任务,微任务的概念。
console.log(1)
Promise.resolve().then(() => {
console.log('promise')
})
setTimeout(() => {
console.log('settimeout')
})
宏任务
每次执行栈执行的代码就是一个宏任务,包括把任务队列的事件加入到主线程的执行,一个宏任务的执行相当于一个task,浏览器会在一个task结束之后对页面进行重新渲染。
常见的macrotask:setTimeout、setInterval、setImmediate、I/O等
微任务
microtask又是个什么东东?在一个task执行之后紧跟着执行的东西,也就是一个task执行完毕,接下来会把它在执行期间产生等所有微任务都进行执行,也就是要把微任务队列执行清空掉,接下来浏览器开始对页面进行重新渲染。
常见的microtask:Promise.then、process.nextTick、MutaionObserver
小结
结合上宏任务、微任务再来总结一下js的事件循环机制。
- 执行同步代码,也就是开始一个宏任务的执行
- 如果遇到异步,等异步任务有了运行结果后再把他们放到事件队列,如果属于微任务的话加入微任务队列。
- 宏任务执行完毕,接下来把这个task执行过程中产生的微任务依次执行
- 微任务全部执行完毕,浏览器开始重新渲染
- 去事件队列读取下一个宏任务,不断循环上面过程。
// 举个栗子
console.log('1');
setTimeout(function() {
console.log('2');
Promise.resolve().then(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
}, 100)
Promise.resolve().then(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
Promise.resolve().then(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
console.log('13')
第一次事件循环,首先打印了1,然后遇到setTimeout,我们记为setA,setA去外面排队,接下来Promise.then属于微任务,加入微任务队列。再往下遇到new Promise,直接执行打印7,then函数加入微任务队列。再继续又遇到setTimeout,我们记为setB,排队等着。然后打印13。所以第一轮事件循环结束,我们直接打印了1,7,13,紧接着依次打印微任务队列里面的6,8。
现在第一轮事件循环结束,还有setA,setB等待执行,先执行哪个呢?答案是setB。虽然定时器线程先捕捉到了setA,但是setA延迟时间是100ms,而setB是立即执行的,setB会在setA之前被加入到事件队列。所以当第一轮事件循环结束,此时会把setB从事件队列拉到执行栈中执行,第二轮事件循环开始了。
第二次循环首先打印了9,然后遇到微任务,把10加入到微任务队列,往下又遇到了new Promise直接执行,打印11,then函数的12被加入到微任务,本次事件循环结束。依次打印了9,11,10,12.
第三轮事件循环开始,setA所在的宏任务,首先打印了2,然后3被加入到微任务,同样的,打印4,then函数的5被加入到微任务队列。本轮事件循环结束,依次打印2,4,3,5.
最终结果:1,7,13,6,8,9,11,10,12,2,4,3,5
参考资料:https://dailc.github.io