学习js,事件队列一定是一个高频出现的词汇,主线程+事件队列是它的一大特色。
最近在回顾旧的知识点,碰到微任务这个概念,这里记录下,先根据这类面试题写个demo:
setTimeout(function() { console.log('5'); }); new Promise(function(resolve) { console.log('1'); // setTimeout(resolve); resolve() }) .then(function() { setTimeout(() => { console.log('6'); }); return '4'; }) .then(console.log); process.nextTick(() => { console.log('3'); }); console.log('2');
之前自己写了一个类似Promise的类,then接收的函数直接用setTimeout来push进一个函数队列,虽说功能是实现了,其在浏览器端的表现却不符合实际,一些网站还有干脆用process.nextTick来实现的,这样对浏览器就不再适用。
根据文档可以发现浏览器里有一个微任务队列是在任务队列之前触发的,可以理解成node环境下的setImmediate这样一个东西,关于Promise具体用什么异步方法实现在mdn的文档说得也很明白:
JavaScript promises and the Mutation Observer API both use the microtask queue to run their callbacks.
打开较新版的谷歌浏览器控制台,输入queueMicroTask可以发现window环境下确实有这么个函数,和node的process.nextTick一样接收函数来异步运行(不同之处是nextTick是在主线程最后运行且非异步),而且先于一般的任务队列执行:
microtasks can enqueue new microtasks and those new microtasks will execute before the next task begins to run, and before the end of the current event loop iteration.
其实这个微任务队列的函数有点类似于另一个浏览器每帧绑定的钩子函数requestAnimationFrame,即传入的函数组成一个队列在特定的时间点执行,在这里可以理解成整个任务队列的最开始是执行微任务队列。
上面代码在node环境下会按顺序打印出 1,2,3,4,5,6,了解了微任务的机制,就不难理解结果了。
主进程包括:promise内的同步代码,console.log('2') // 1,2
主进程最后运行:promise.nextTick 的入参 // 3
第一轮微任务队列:function(){ ... return '4'},console.log // 这里由于resolve是立即执行的,其代表的function(){...return '4'}将会被推入第一轮event loop 的微任务队列,如果用setTimeout包裹,则会进入第二轮的微任务队列;于此同时,function(){...return '4'}内部的setTImeout包裹的函数进入第二轮任务队列; 打印 4
第一轮任务:setTimeout包括的console.log('5') // 5
第二轮任务:function(){...return '4'} 中已进入第二轮的函数 // 6
实际编写代码,这些异步执行的函数不会以延迟为0的情形触发,比如ajax请求,因此了解event loop除了用来出题的时候有对策,好像并没有什么实际应用的价值。