宿主环境javascript
window
、document
、setTimeout
、脱离浏览器就不会有这些API。浏览器中的功能模块:一个模块占用一个线程。java
> JS是单线程的缘由是浏览器只会开一个JS执行引擎
setTimeout
,promise
等能够调用其它线程的API才能执行异步代码。用于存放执行栈清空后执行的异步代码,一般从计时器、网络请求、事件监听等其它线程中获取。node
setTimeout
等函数时,会调用宿主中的其它线程(计时器);图解:segmentfault
举个例子:这里以setTimeout
为例,其它异步方法都是同样的promise
setTimeout(function bar(){ console.log('计时器回调执行'); }, 0) function foo(){ console.log('foo函数执行'); } foo();
//执行过程伪代码: 1. 执行栈入栈setTimeout函数; 执行栈:[setTimeout] 计时线程:[] 事件队列:[] 2. 浏览器遇到其它线程API,通知对应线程执行代码,此时浏览器唤起的是计时器线程,将setTimeout方法推入计时器线程; 执行栈:[] 计时线程:[setTimeout] 事件队列:[] 3. 执行栈继续执行后续代码,入栈foo函数,同时,计时线程中的setTimeout在计时; 执行栈:[foo] 计时线程:[setTimeout] 事件队列:[] 4. 计时完毕,计时线程将setTime方法的回调函数推入事件队列,而后计时器线程中的setTimeout执行完毕被销毁; 执行栈:[foo] 计时线程:[] 事件队列:[bar] 5. 执行foo函数,打印'foo函数执行',foo执行完毕,foo函数出栈; 执行栈:[] 计时线程:[] 事件队列:[bar] 6. 执行栈没有可执行代码,会隔一段时间去事件队列中查看(事件轮询)一下有没有可执行代码,此时查看到有一个bar函数,将bar函数推入执行栈; 执行栈:[bar] 计时线程:[] 事件队列:[] 7. 执行bar函数,打印'计时器回调执行',bar执行完毕,bar函数出栈; 执行栈:[] 计时线程:[] 事件队列:[]
从上述例子能够看出:浏览器
- setTimeout的计时并不许确,计时完成后只是将回调函数推入事件队列,并非立刻执行,还要等执行栈清空后才会执行;
- 无论setTimeout的方法是否是写在最前面,计时时间有多短,都是在同步代码执行完毕后才会执行。
事件队列又能够细分为宏队列和微队列,每次执行栈清空后会先执行微队列中的任务,再执行宏队列中的任务;微信
Promise.then
、mutationObserver
举个例子:网络
setTimeout(function(){ console.log('计时完成'); //3. 最后执行宏任务中的队列 }, 0); new Promise(function(resolve, reject){ console.log('promise 代码执行'); //1. 先执行,这里的代码是同步的 resolve(); }).then(function(){ console.log('promise resolve'); //2. 执行栈空了以后,先执行微队列中的任务 })
无论宏任务中的代码写在哪里,都是在微任务执行后再执行。由于浏览器会在微队列清空后再去看宏队列。