上篇文章说了一下js中定时器 setTimeout 和 setInterval 的执行原理。这两个计时器都是异步执行的,这部分体现了js的执行过程。
js自身执行的模型是事件循环(event loop)。
js在启动时会创建一个循环,每次循环都会去任务队列(task query)里找要进行的任务。当队列里的一个任务执行完毕,该循环会去找下一个任务去执行。js的执行过程就是该循环不断地执行。像定时器,用户触发的事件,promise 等异步执行的代码,都会按规定往这个任务队列推。
任务队列不是只有一个,有 microtasks 和 macrotasks 之分。macrotask query 就是 task query,每个 macrotask 里都有一个 microtask。js开始执行时,会先取一个 macrotask,然后取里面的 microtask 去执行。当前的 microtask 执行完毕之后,会取下一个 microtask 直到 microtask 执行完,然后退出该 macrotask,去执行下一个 macrotask,然后就一直循环下去。就相当于有一个 task query,而这个队列中的每一个 task,也相当于一个 task query。
macrotask 会放到下一个事件循环中去,而 microtask 会放到当前事件循环的最后,因此我们观察到的现象直观地来说,就是这些异步方法有一个优先级,microtask 要比 macrotask 优先级高,优先级高的会比优先级低的先执行,同优先级的就根据进入队列的时间去执行。所以在同步的代码中,不管 microtask 和 macrotask 谁先放入队列,都是 microtask 先执行,macrotask 后执行。
macrotasks: setTimeout setInterval setImmediate I/O UI渲染
microtasks: Promise process.nextTick Object.observe MutationObserver
看一个例子:
1 console.log('start') 2 3 Promise.resolve().then(() => { 4 console.log('promise 1') 5 }) 6 7 setTimeout(() => { 8 console.log('setTimeout 1') 9 Promise.resolve().then(() => { 10 console.log('promise 2') 11 Promise.resolve().then(() => { 12 console.log('promise 3') 13 }).then(() => { 14 console.log('promise 4') 15 }) 16 }).then(() => { 17 setTimeout(() => { 18 console.log('setTimeout 2') 19 Promise.resolve().then(() => { 20 console.log('promise 5') 21 }) 22 }, 0) 23 console.log('promise 6') 24 }) 25 console.log('setTimeout 2') 26 }, 0) 27 28 setTimeout(() => { 29 console.log('setTimeout 3') 30 Promise.resolve().then(() => { 31 console.log('promise 7') 32 }) 33 console.log('setTimeout 4') 34 }, 0) 35 36 Promise.resolve().then(() => { 37 console.log('promise 8') 38 })
执行结果为
----------------------------------
1 Promise.resolve().then(() => { 2 console.log('promise 1') 3 Promise.resolve().then(() => { 4 console.log('promise 2') 5 }).then(() => { 6 console.log('promise 3') 7 }) 8 }).then(() => { 9 console.log('promise 4') 10 })
我说的那一部分,单独抽出来就是上面这段,其运行结果为
promise 1
promise 2
promise 4
promise 3
这个问题其实是 promise 的原理,我这里就不多说了,大家去看 promise 的原理应该可以找到答案。