先解释一下主任务、微任务和宏任务的概念。
因为JS是单线程,代码中所有同步任务(后面统一称为主任务)都在主线程上执行,形成一个执行栈,栈是先进后出的。代码跑起来以后会先执行栈中的主任务直到所有任务执行完毕;当遇到异步任务时,它会被单独放到异步执行队列中去,这里的异步任务又分为宏任务和微任务,有一个宏任务队列和一个微任务队列,队列是先进先出的。
在异步任务中,微任务优于宏任务执行,当主任务执行完毕后先去查看微任务队列中是否有东西,有则主线程读取微任务队列中的所有内容按顺序执行完毕(这个过程中遇到了异步任务也是要放到两个异步队列的末尾的,后面主线程中内容执行完毕后又会去读取,所以可能产生事件循环Event Loop,这是前端必须了解的一个概念)。当主任务栈和微任务队列中没有内容了,主线程才会读取宏任务队列中的所有内容按顺序执行完毕。
所以,执行顺序:主任务 > 微任务 > 宏任务
而setTimeout属于异步任务中的宏任务。当代码按顺序读到setTimeout函数时,会执行它开始计时,计时完成后把setTimeout函数里面的内容放入宏任务队列中等待后续被主线程读取。
- 如果setTimeout的时间设置非常大,当主任务和微任务中的内容都执行完毕时间都还没结束,那么会等到时间到了再进入宏任务队列并被主线程读取。
- 如果setTimeout的时间设置比较小,在主任务和微任务中的内容执行完毕之前就结束了,那么它在时间到了以后就会被加入宏任务队列,并且当主任务和微任务内容读取完毕以后会立刻调用宏任务中加入的setTimeout函数里面的内容。只有同步任务执行完了才会去执行异步任务,哪怕异步任务已经到时间了。
- 在同一环境中有多个setTimeout函数时(都在主任务或者都在微任务中),setTimeout函数中内容被放入宏任务队列的顺序与代码书写的顺序无关,而是与计时器时间设定的长短有关,时间越长则越后加入队列,也越后被读取执行。